diff --git a/src/main/java/com/huaheng/common/constant/GenConstants.java b/src/main/java/com/huaheng/common/constant/GenConstants.java
new file mode 100644
index 0000000..911d50d
--- /dev/null
+++ b/src/main/java/com/huaheng/common/constant/GenConstants.java
@@ -0,0 +1,177 @@
+package com.huaheng.common.constant;
+
+import java.time.LocalTime;
+
+/**
+ * 代码生成通用常量
+ */
+public class GenConstants {
+    /**
+     * 单表(增删改查)
+     */
+    public static final String TPL_CRUD = "crud";
+
+    /**
+     * 树表(增删改查)
+     */
+    public static final String TPL_TREE = "tree";
+
+    /**
+     * 主子表(增删改查)
+     */
+    public static final String TPL_SUB = "sub";
+
+    /**
+     * 树编码字段
+     */
+    public static final String TREE_CODE = "treeCode";
+
+    /**
+     * 树父编码字段
+     */
+    public static final String TREE_PARENT_CODE = "treeParentCode";
+
+    /**
+     * 树名称字段
+     */
+    public static final String TREE_NAME = "treeName";
+
+    /**
+     * 数据库字符串类型
+     */
+    public static final String[] COLUMNTYPE_STR = {"char", "varchar", "narchar", "varchar2", "tinytext", "text",
+            "mediumtext", "longtext"};
+
+    /**
+     * 数据库时间类型
+     */
+    public static final String[] COLUMNTYPE_DATETIME = {"datetime", "timestamp"};
+
+    /**
+     * 数据库时间类型
+     */
+    public static final String[] COLUMNTYPE_DATE = {"date"};
+
+    /**
+     * 数据库时间类型
+     */
+    public static final String[] COLUMNTYPE_TIME = {"time"};
+
+    /**
+     * 数据库位类型
+     */
+    public static final String[] COLUMNTYPE_BIT = {"bit"};
+
+    /**
+     * 数据库数字类型
+     */
+    public static final String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer",
+            "bigint", "float", "float", "double", "decimal"};
+
+    /**
+     * 页面不需要编辑字段
+     */
+    public static final String[] COLUMNNAME_NOT_EDIT = {"id", "create_by", "create_time", "del_flag"};
+
+    /**
+     * 页面不需要显示的列表字段
+     */
+    public static final String[] COLUMNNAME_NOT_LIST = {"id", "create_by", "create_time", "del_flag", "update_by",
+            "update_time"};
+
+    /**
+     * 页面不需要查询字段
+     */
+    public static final String[] COLUMNNAME_NOT_QUERY = {"id", "create_by", "create_time", "del_flag", "update_by",
+            "update_time", "remark"};
+
+    /**
+     * Entity基类字段
+     */
+    public static final String[] BASE_ENTITY = {"createBy", "createTime", "updateBy", "updateTime", "remark"};
+
+    /**
+     * Tree基类字段
+     */
+    public static final String[] TREE_ENTITY = {"parentName", "parentId", "orderNum", "ancestors"};
+
+    /**
+     * 文本框
+     */
+    public static final String HTML_INPUT = "input";
+
+    /**
+     * 文本域
+     */
+    public static final String HTML_TEXTAREA = "textarea";
+
+    /**
+     * 下拉框
+     */
+    public static final String HTML_SELECT = "select";
+
+    /**
+     * 单选框
+     */
+    public static final String HTML_RADIO = "radio";
+
+    /**
+     * 复选框
+     */
+    public static final String HTML_CHECKBOX = "checkbox";
+
+    /**
+     * 日期控件
+     */
+    public static final String HTML_DATETIME = "datetime";
+
+    /**
+     * 字符串类型
+     */
+    public static final String TYPE_STRING = "String";
+
+    /**
+     * 整型
+     */
+    public static final String TYPE_INTEGER = "Integer";
+
+    /**
+     * 长整型
+     */
+    public static final String TYPE_LONG = "Long";
+
+    /**
+     * 浮点型
+     */
+    public static final String TYPE_DOUBLE = "Double";
+
+    /**
+     * 高精度计算类型
+     */
+    public static final String TYPE_BIGDECIMAL = "BigDecimal";
+
+    /**
+     * 时间类型
+     */
+    public static final String TYPE_DATE = "LocalDate";
+
+    /**
+     * 时间类型
+     */
+    public static final String TYPE_DATETIME = "LocalDateTime";
+
+    /**
+     * 时间类型
+     */
+    public static final String TYPE_TIME = "LocalTime";
+
+    /**
+     * 模糊查询
+     */
+    public static final String QUERY_LIKE = "LIKE";
+
+    /**
+     * 需要
+     */
+    public static final String REQUIRE = "1";
+}
diff --git a/src/main/java/com/huaheng/common/constant/UserConstants.java b/src/main/java/com/huaheng/common/constant/UserConstants.java
index 82fa79d..1663d1b 100644
--- a/src/main/java/com/huaheng/common/constant/UserConstants.java
+++ b/src/main/java/com/huaheng/common/constant/UserConstants.java
@@ -8,8 +8,8 @@ package com.huaheng.common.constant;
 public class UserConstants
 {
 
-//    /** 正常状态 */
-//    public static final String NORMAL = "0";
+    /** 正常状态 */
+    public static final String NORMAL = "0";
 //
 //    /** 异常状态 */
 //    public static final String EXCEPTION = "1";
@@ -89,4 +89,6 @@ public class UserConstants
      */
     public static final String EMAIL_PATTERN = "^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?";
 
+    /** 字典正常状态 */
+    public static final String DICT_NORMAL = "0";
 }
diff --git a/src/main/java/com/huaheng/common/utils/StringUtils.java b/src/main/java/com/huaheng/common/utils/StringUtils.java
index e797286..c3e281f 100644
--- a/src/main/java/com/huaheng/common/utils/StringUtils.java
+++ b/src/main/java/com/huaheng/common/utils/StringUtils.java
@@ -3,129 +3,123 @@ package com.huaheng.common.utils;
 import java.lang.reflect.Field;
 import java.util.Collection;
 import java.util.Map;
+
 import org.apache.commons.lang.text.StrBuilder;
 import com.huaheng.common.support.StrFormatter;
 
 /**
  * 字符串工具类
- * 
+ *
  * @author huaheng
  */
-public class StringUtils extends org.apache.commons.lang3.StringUtils
-{
-    /** 空字符串 */
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+    /**
+     * 空字符串
+     */
     private static final String NULLSTR = "";
 
-    /** 下划线 */
+    /**
+     * 下划线
+     */
     private static final char SEPARATOR = '_';
 
     /**
      * 获取参数不为空值
-     * 
+     *
      * @param value defaultValue 要判断的value
      * @return value 返回值
      */
-    public static <T> T nvl(T value, T defaultValue)
-    {
+    public static <T> T nvl(T value, T defaultValue) {
         return value != null ? value : defaultValue;
     }
 
     /**
      * * 判断一个Collection是否为空, 包含List,Set,Queue
-     * 
+     *
      * @param coll 要判断的Collection
      * @return true:为空 false:非空
      */
-    public static boolean isEmpty(Collection<?> coll)
-    {
+    public static boolean isEmpty(Collection<?> coll) {
         return isNull(coll) || coll.isEmpty();
     }
 
     /**
      * * 判断一个Collection是否非空,包含List,Set,Queue
-     * 
+     *
      * @param coll 要判断的Collection
      * @return true:非空 false:空
      */
-    public static boolean isNotEmpty(Collection<?> coll)
-    {
+    public static boolean isNotEmpty(Collection<?> coll) {
         return !isEmpty(coll);
     }
 
     /**
      * * 判断一个对象数组是否为空
-     * 
+     *
      * @param objects 要判断的对象数组
-     ** @return true:为空 false:非空
+     *                * @return true:为空 false:非空
      */
-    public static boolean isEmpty(Object[] objects)
-    {
+    public static boolean isEmpty(Object[] objects) {
         return isNull(objects) || (objects.length == 0);
     }
 
     /**
      * * 判断一个对象数组是否非空
-     * 
+     *
      * @param objects 要判断的对象数组
      * @return true:非空 false:空
      */
-    public static boolean isNotEmpty(Object[] objects)
-    {
+    public static boolean isNotEmpty(Object[] objects) {
         return !isEmpty(objects);
     }
 
     /**
      * * 判断一个Map是否为空
-     * 
+     *
      * @param map 要判断的Map
      * @return true:为空 false:非空
      */
-    public static boolean isEmpty(Map<?, ?> map)
-    {
+    public static boolean isEmpty(Map<?, ?> map) {
         return isNull(map) || map.isEmpty();
     }
 
     /**
      * * 判断一个Map是否为空
-     * 
+     *
      * @param map 要判断的Map
      * @return true:非空 false:空
      */
-    public static boolean isNotEmpty(Map<?, ?> map)
-    {
+    public static boolean isNotEmpty(Map<?, ?> map) {
         return !isEmpty(map);
     }
 
     /**
      * * 判断一个字符串是否为空串
-     * 
+     *
      * @param str String
      * @return true:为空 false:非空
      */
-    public static boolean isEmpty(String str)
-    {
+    public static boolean isEmpty(String str) {
         return isNull(str) || NULLSTR.equals(str.trim());
     }
 
     /**
      * * 判断一个字符串是否为非空串
-     * 
+     *
      * @param str String
      * @return true:非空串 false:空串
      */
-    public static boolean isNotEmpty(String str)
-    {
+    public static boolean isNotEmpty(String str) {
         return !isEmpty(str);
     }
 
     /**
      * * 判断一个对象是否为空
-     * 
+     *
      * @param object Object
      * @return true:为空 false:非空
      */
-    public static boolean isNull(Object object)
-    {
+    public static boolean isNull(Object object) {
 
         if (object == null) {
             return true;
@@ -161,59 +155,51 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
 
     /**
      * * 判断一个对象是否非空
-     * 
+     *
      * @param object Object
      * @return true:非空 false:空
      */
-    public static boolean isNotNull(Object object)
-    {
+    public static boolean isNotNull(Object object) {
         return !isNull(object);
     }
 
     /**
      * * 判断一个对象是否是数组类型(Java基本型别的数组)
-     * 
+     *
      * @param object 对象
      * @return true:是数组 false:不是数组
      */
-    public static boolean isArray(Object object)
-    {
+    public static boolean isArray(Object object) {
         return isNotNull(object) && object.getClass().isArray();
     }
 
     /**
      * 去空格
      */
-    public static String trim(String str)
-    {
+    public static String trim(String str) {
         return (str == null ? "" : str.trim());
     }
 
     /**
      * 截取字符串
-     * 
-     * @param str 字符串
+     *
+     * @param str   字符串
      * @param start 开始
      * @return 结果
      */
-    public static String substring(final String str, int start)
-    {
-        if (str == null)
-        {
+    public static String substring(final String str, int start) {
+        if (str == null) {
             return NULLSTR;
         }
 
-        if (start < 0)
-        {
+        if (start < 0) {
             start = str.length() + start;
         }
 
-        if (start < 0)
-        {
+        if (start < 0) {
             start = 0;
         }
-        if (start > str.length())
-        {
+        if (start > str.length()) {
             return NULLSTR;
         }
 
@@ -222,44 +208,36 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
 
     /**
      * 截取字符串
-     * 
-     * @param str 字符串
+     *
+     * @param str   字符串
      * @param start 开始
-     * @param end 结束
+     * @param end   结束
      * @return 结果
      */
-    public static String substring(final String str, int start, int end)
-    {
-        if (str == null)
-        {
+    public static String substring(final String str, int start, int end) {
+        if (str == null) {
             return NULLSTR;
         }
 
-        if (end < 0)
-        {
+        if (end < 0) {
             end = str.length() + end;
         }
-        if (start < 0)
-        {
+        if (start < 0) {
             start = str.length() + start;
         }
 
-        if (end > str.length())
-        {
+        if (end > str.length()) {
             end = str.length();
         }
 
-        if (start > end)
-        {
+        if (start > end) {
             return NULLSTR;
         }
 
-        if (start < 0)
-        {
+        if (start < 0) {
             start = 0;
         }
-        if (end < 0)
-        {
+        if (end < 0) {
             end = 0;
         }
 
@@ -274,15 +252,13 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
      * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
      * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
      * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
-     * 
+     *
      * @param template 文本模板,被替换的部分用 {} 表示
-     * @param params 参数值
+     * @param params   参数值
      * @return 格式化后的文本
      */
-    public static String format(String template, Object... params)
-    {
-        if (isEmpty(params) || isEmpty(template))
-        {
+    public static String format(String template, Object... params) {
+        if (isEmpty(params) || isEmpty(template)) {
             return template;
         }
         return StrFormatter.format(template, params);
@@ -291,11 +267,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
     /**
      * 驼峰首字符小写
      */
-    public static String uncapitalize(String str)
-    {
+    public static String uncapitalize(String str) {
         int strLen;
-        if (str == null || (strLen = str.length()) == 0)
-        {
+        if (str == null || (strLen = str.length()) == 0) {
             return str;
         }
         return new StrBuilder(strLen).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString();
@@ -304,35 +278,27 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
     /**
      * 下划线转驼峰命名
      */
-    public static String toUnderScoreCase(String s)
-    {
-        if (s == null)
-        {
+    public static String toUnderScoreCase(String s) {
+        if (s == null) {
             return null;
         }
         StringBuilder sb = new StringBuilder();
         boolean upperCase = false;
-        for (int i = 0; i < s.length(); i++)
-        {
+        for (int i = 0; i < s.length(); i++) {
             char c = s.charAt(i);
 
             boolean nextUpperCase = true;
 
-            if (i < (s.length() - 1))
-            {
+            if (i < (s.length() - 1)) {
                 nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
             }
 
-            if ((i > 0) && Character.isUpperCase(c))
-            {
-                if (!upperCase || !nextUpperCase)
-                {
+            if ((i > 0) && Character.isUpperCase(c)) {
+                if (!upperCase || !nextUpperCase) {
                     sb.append(SEPARATOR);
                 }
                 upperCase = true;
-            }
-            else
-            {
+            } else {
                 upperCase = false;
             }
 
@@ -344,19 +310,15 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
 
     /**
      * 是否包含字符串
-     * 
-     * @param str 验证字符串
+     *
+     * @param str  验证字符串
      * @param strs 字符串组
      * @return 包含返回true
      */
-    public static boolean inStringIgnoreCase(String str, String... strs)
-    {
-        if (str != null && strs != null)
-        {
-            for (String s : strs)
-            {
-                if (str.equalsIgnoreCase(trim(s)))
-                {
+    public static boolean inStringIgnoreCase(String str, String... strs) {
+        if (str != null && strs != null) {
+            for (String s : strs) {
+                if (str.equalsIgnoreCase(trim(s))) {
                     return true;
                 }
             }
@@ -366,31 +328,25 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
 
     /**
      * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
-     * 
+     *
      * @param name 转换前的下划线大写方式命名的字符串
      * @return 转换后的驼峰式命名的字符串
      */
-    public static String convertToCamelCase(String name)
-    {
+    public static String convertToCamelCase(String name) {
         StringBuilder result = new StringBuilder();
         // 快速检查
-        if (name == null || name.isEmpty())
-        {
+        if (name == null || name.isEmpty()) {
             // 没必要转换
             return "";
-        }
-        else if (!name.contains("_"))
-        {
+        } else if (!name.contains("_")) {
             // 不含下划线,仅将首字母大写
             return name.substring(0, 1).toUpperCase() + name.substring(1);
         }
         // 用下划线将原始字符串分割
         String[] camels = name.split("_");
-        for (String camel : camels)
-        {
+        for (String camel : camels) {
             // 跳过原始字符串中开头、结尾的下换线或双重下划线
-            if (camel.isEmpty())
-            {
+            if (camel.isEmpty()) {
                 continue;
             }
             // 首字母大写
@@ -399,4 +355,29 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
         }
         return result.toString();
     }
+
+    /**
+     * 驼峰式命名法 例如:user_name->userName
+     */
+    public static String toCamelCase(String s) {
+        if (s == null) {
+            return null;
+        }
+        s = s.toLowerCase();
+        StringBuilder sb = new StringBuilder(s.length());
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+
+            if (c == SEPARATOR) {
+                upperCase = true;
+            } else if (upperCase) {
+                sb.append(Character.toUpperCase(c));
+                upperCase = false;
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/huaheng/common/utils/VelocityInitializer.java b/src/main/java/com/huaheng/common/utils/VelocityInitializer.java
new file mode 100644
index 0000000..f52c1c1
--- /dev/null
+++ b/src/main/java/com/huaheng/common/utils/VelocityInitializer.java
@@ -0,0 +1,32 @@
+package com.huaheng.common.utils;
+
+import java.util.Properties;
+
+import com.huaheng.common.constant.Constants;
+import org.apache.velocity.app.Velocity;
+
+
+/**
+ * VelocityEngine工厂
+ *
+ * @author RuoYi
+ */
+public class VelocityInitializer {
+    /**
+     * 初始化vm方法
+     */
+    public static void initVelocity() {
+        Properties p = new Properties();
+        try {
+            // 加载classpath目录下的vm文件
+            p.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+            // 定义字符集
+            p.setProperty(Velocity.ENCODING_DEFAULT, Constants.UTF8);
+            p.setProperty(Velocity.OUTPUT_ENCODING, Constants.UTF8);
+            // 初始化Velocity引擎,指定配置Properties
+            Velocity.init(p);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/src/main/java/com/huaheng/common/utils/VelocityUtils.java b/src/main/java/com/huaheng/common/utils/VelocityUtils.java
new file mode 100644
index 0000000..1d2667b
--- /dev/null
+++ b/src/main/java/com/huaheng/common/utils/VelocityUtils.java
@@ -0,0 +1,306 @@
+package com.huaheng.common.utils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import com.huaheng.common.constant.GenConstants;
+import com.huaheng.common.utils.DateUtils;
+import com.huaheng.common.utils.StringUtils;
+import com.huaheng.framework.config.GenConfig;
+import com.huaheng.pc.tool.gen.domain.GenTable;
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+import org.apache.velocity.VelocityContext;
+import com.alibaba.fastjson.JSONObject;
+
+public class VelocityUtils {
+    /**
+     * 项目空间路径
+     */
+    private static final String PROJECT_PATH = "main/java";
+
+    /**
+     * mybatis空间路径
+     */
+    private static final String MYBATIS_PATH = "main/resources/mapper";
+
+    /**
+     * html空间路径
+     */
+    private static final String TEMPLATES_PATH = "main/resources/templates";
+
+    /**
+     * 设置模板变量信息
+     *
+     * @return 模板列表
+     */
+    public static VelocityContext prepareContext(GenTable genTable) {
+        String moduleName = genTable.getModuleName();
+        String businessName = genTable.getBusinessName();
+        String packageName = genTable.getPackageName();
+        String tplCategory = genTable.getTplCategory();
+        String functionName = genTable.getFunctionName();
+
+        VelocityContext velocityContext = new VelocityContext();
+        velocityContext.put("tplCategory", genTable.getTplCategory());
+        velocityContext.put("tableName", genTable.getTableName());
+        velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
+        velocityContext.put("ClassName", genTable.getClassName());
+        velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
+        velocityContext.put("moduleName", genTable.getModuleName());
+        velocityContext.put("businessName", genTable.getBusinessName());
+        velocityContext.put("basePackage", getPackagePrefix(packageName));
+        velocityContext.put("packageName", packageName);
+        velocityContext.put("author", genTable.getFunctionAuthor());
+        velocityContext.put("datetime", DateUtils.getDate());
+        velocityContext.put("pkColumn", genTable.getPkColumn());
+        velocityContext.put("importList", getImportList(genTable));
+        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
+        velocityContext.put("columns", genTable.getColumns());
+        velocityContext.put("table", genTable);
+        if (GenConstants.TPL_TREE.equals(tplCategory)) {
+            setTreeVelocityContext(velocityContext, genTable);
+        }
+        if (GenConstants.TPL_SUB.equals(tplCategory)) {
+            setSubVelocityContext(velocityContext, genTable);
+        }
+        return velocityContext;
+    }
+
+    public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSONObject.parseObject(options);
+        String treeCode = getTreecode(paramsObj);
+        String treeParentCode = getTreeParentCode(paramsObj);
+        String treeName = getTreeName(paramsObj);
+
+        context.put("treeCode", treeCode);
+        context.put("treeParentCode", treeParentCode);
+        context.put("treeName", treeName);
+        context.put("expandColumn", getExpandColumn(genTable));
+        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+            context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE));
+        }
+        if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
+            context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME));
+        }
+    }
+
+    public static void setSubVelocityContext(VelocityContext context, GenTable genTable) {
+        GenTable subTable = genTable.getSubTable();
+        String subTableName = genTable.getSubTableName();
+        String subTableFkName = genTable.getSubTableFkName();
+        String subClassName = genTable.getSubTable().getClassName();
+        String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName);
+
+        context.put("subTable", subTable);
+        context.put("subTableName", subTableName);
+        context.put("subTableFkName", subTableFkName);
+        context.put("subTableFkClassName", subTableFkClassName);
+        context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName));
+        context.put("subClassName", subClassName);
+        context.put("subclassName", StringUtils.uncapitalize(subClassName));
+        context.put("subImportList", getImportList(genTable.getSubTable()));
+    }
+
+    /**
+     * 获取模板信息
+     *
+     * @return 模板列表
+     */
+    public static List<String> getTemplateList(String tplCategory) {
+        List<String> templates = new ArrayList<String>();
+        templates.add("templates/vm/java/domain.java.vm");
+        templates.add("templates/vm/java/mapper.java.vm");
+        templates.add("templates/vm/java/service.java.vm");
+        templates.add("templates/vm/java/serviceImpl.java.vm");
+        templates.add("templates/vm/java/controller.java.vm");
+        templates.add("templates/vm/xml/mapper.xml.vm");
+        if (GenConstants.TPL_CRUD.equals(tplCategory)) {
+            templates.add("templates/vm/html/list.html.vm");
+        } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
+            templates.add("templates/vm/html/tree.html.vm");
+            templates.add("templates/vm/html/list-tree.html.vm");
+        } else if (GenConstants.TPL_SUB.equals(tplCategory)) {
+            templates.add("templates/vm/html/list.html.vm");
+            templates.add("templates/vm/java/sub-domain.java.vm");
+        }
+        templates.add("templates/vm/html/add.html.vm");
+        templates.add("templates/vm/html/edit.html.vm");
+        templates.add("templates/vm/sql/sql.vm");
+        return templates;
+    }
+
+    /**
+     * 获取文件名
+     */
+    public static String getFileName(String template, GenTable genTable) {
+        // 文件名称
+        String fileName = "";
+        // 包路径
+        String packageName = genTable.getPackageName();
+        // 模块名
+        String moduleName = genTable.getModuleName();
+        // 大写类名
+        String className = genTable.getClassName();
+        // 业务名称
+        String businessName = genTable.getBusinessName();
+
+        String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
+        String mybatisPath = MYBATIS_PATH + "/" + moduleName;
+        String htmlPath = TEMPLATES_PATH + "/" + moduleName + "/" + businessName;
+
+        if (template.contains("domain.java.vm")) {
+            fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
+        }
+        if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) {
+            fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName());
+        } else if (template.contains("mapper.java.vm")) {
+            fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
+        } else if (template.contains("service.java.vm")) {
+            fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
+        } else if (template.contains("serviceImpl.java.vm")) {
+            fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
+        } else if (template.contains("controller.java.vm")) {
+            fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
+        } else if (template.contains("mapper.xml.vm")) {
+            fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
+        } else if (template.contains("list.html.vm")) {
+            fileName = StringUtils.format("{}/{}.html", htmlPath, businessName);
+        } else if (template.contains("list-tree.html.vm")) {
+            fileName = StringUtils.format("{}/{}.html", htmlPath, businessName);
+        } else if (template.contains("tree.html.vm")) {
+            fileName = StringUtils.format("{}/tree.html", htmlPath);
+        } else if (template.contains("add.html.vm")) {
+            fileName = StringUtils.format("{}/add.html", htmlPath);
+        } else if (template.contains("edit.html.vm")) {
+            fileName = StringUtils.format("{}/edit.html", htmlPath);
+        } else if (template.contains("sql.vm")) {
+            fileName = businessName + "Menu.sql";
+        }
+        return fileName;
+    }
+
+    /**
+     * 获取项目文件路径
+     *
+     * @return 路径
+     */
+    public static String getProjectPath() {
+        String packageName = GenConfig.getPackageName();
+        StringBuffer projectPath = new StringBuffer();
+        projectPath.append("main/java/");
+        projectPath.append(packageName.replace(".", "/"));
+        projectPath.append("/");
+        return projectPath.toString();
+    }
+
+    /**
+     * 获取包前缀
+     *
+     * @param packageName 包名称
+     * @return 包前缀名称
+     */
+    public static String getPackagePrefix(String packageName) {
+        int lastIndex = packageName.lastIndexOf(".");
+        String basePackage = StringUtils.substring(packageName, 0, lastIndex);
+        return basePackage;
+    }
+
+    /**
+     * 根据列类型获取导入包
+     *
+     * @param genTable 业务表对象
+     * @return 返回需要导入的包列表
+     */
+    public static HashSet<String> getImportList(GenTable genTable) {
+        List<GenTableColumn> columns = genTable.getColumns();
+        GenTable subGenTable = genTable.getSubTable();
+        HashSet<String> importList = new HashSet<String>();
+        if (StringUtils.isNotNull(subGenTable)) {
+            importList.add("java.util.List");
+        }
+        for (GenTableColumn column : columns) {
+            if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
+                importList.add("java.util.Date");
+            } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
+                importList.add("java.math.BigDecimal");
+            }
+        }
+        return importList;
+    }
+
+    /**
+     * 获取权限前缀
+     *
+     * @param moduleName   模块名称
+     * @param businessName 业务名称
+     * @return 返回权限前缀
+     */
+    public static String getPermissionPrefix(String moduleName, String businessName) {
+        return StringUtils.format("{}:{}", moduleName, businessName);
+
+    }
+
+    /**
+     * 获取树编码
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树编码
+     */
+    public static String getTreecode(JSONObject paramsObj) {
+        if (paramsObj.containsKey(GenConstants.TREE_CODE)) {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE));
+        }
+        return "";
+    }
+
+    /**
+     * 获取树父编码
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树父编码
+     */
+    public static String getTreeParentCode(JSONObject paramsObj) {
+        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE));
+        }
+        return "";
+    }
+
+    /**
+     * 获取树名称
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树名称
+     */
+    public static String getTreeName(JSONObject paramsObj) {
+        if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME));
+        }
+        return "";
+    }
+
+    /**
+     * 获取需要在哪一列上面显示展开按钮
+     *
+     * @param genTable 业务表对象
+     * @return 展开按钮列序号
+     */
+    public static int getExpandColumn(GenTable genTable) {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSONObject.parseObject(options);
+        String treeName = paramsObj.getString(GenConstants.TREE_NAME);
+        int num = 0;
+        for (GenTableColumn column : genTable.getColumns()) {
+            if (column.isList()) {
+                num++;
+                String columnName = column.getColumnName();
+                if (columnName.equals(treeName)) {
+                    break;
+                }
+            }
+        }
+        return num;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/huaheng/framework/config/GenConfig.java b/src/main/java/com/huaheng/framework/config/GenConfig.java
index 9e72b66..d9fb3a9 100644
--- a/src/main/java/com/huaheng/framework/config/GenConfig.java
+++ b/src/main/java/com/huaheng/framework/config/GenConfig.java
@@ -1,70 +1,72 @@
 package com.huaheng.framework.config;
 
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
 import org.springframework.stereotype.Component;
 
 /**
  * 读取代码生成相关配置
- * 
- * @author huaheng
+ *
+ * @author ruoyi
  */
 @Component
 @ConfigurationProperties(prefix = "gen")
-public class GenConfig
-{
-    /** 作者 */
+@PropertySource(value = {"classpath:generator.yml"})
+public class GenConfig {
+    /**
+     * 作者
+     */
     public static String author;
-    /** 生成包路径 */
+
+    /**
+     * 生成包路径
+     */
     public static String packageName;
-    /** 自动去除表前缀,默认是true */
-    public static String autoRemovePre;
-    /** 表前缀(类名不会包含表前缀) */
+
+    /**
+     * 自动去除表前缀,默认是false
+     */
+    public static boolean autoRemovePre;
+
+    /**
+     * 表前缀(类名不会包含表前缀)
+     */
     public static String tablePrefix;
 
-    public static String getAuthor()
-    {
+    public static String getAuthor() {
         return author;
     }
 
-    public void setAuthor(String author)
-    {
+    @Value("${author}")
+    public void setAuthor(String author) {
         GenConfig.author = author;
     }
 
-    public static String getPackageName()
-    {
+    public static String getPackageName() {
         return packageName;
     }
 
-    public void setPackageName(String packageName)
-    {
+    @Value("${packageName}")
+    public void setPackageName(String packageName) {
         GenConfig.packageName = packageName;
     }
 
-    public static String getAutoRemovePre()
-    {
+    public static boolean getAutoRemovePre() {
         return autoRemovePre;
     }
 
-    public void setAutoRemovePre(String autoRemovePre)
-    {
+    @Value("${autoRemovePre}")
+    public void setAutoRemovePre(boolean autoRemovePre) {
         GenConfig.autoRemovePre = autoRemovePre;
     }
 
-    public static String getTablePrefix()
-    {
+    public static String getTablePrefix() {
         return tablePrefix;
     }
 
-    public void setTablePrefix(String tablePrefix)
-    {
+    @Value("${tablePrefix}")
+    public void setTablePrefix(String tablePrefix) {
         GenConfig.tablePrefix = tablePrefix;
     }
-
-    @Override
-    public String toString()
-    {
-        return "GenConfig [getClass()=" + getClass() + ", hashCode()=" + hashCode() + ", toString()=" + super.toString() + "]";
-    }
-
 }
diff --git a/src/main/java/com/huaheng/framework/web/domain/AjaxResult.java b/src/main/java/com/huaheng/framework/web/domain/AjaxResult.java
index ecbfcd3..0f66709 100644
--- a/src/main/java/com/huaheng/framework/web/domain/AjaxResult.java
+++ b/src/main/java/com/huaheng/framework/web/domain/AjaxResult.java
@@ -85,6 +85,14 @@ public class AjaxResult<T> implements Serializable
     /**
      * 返回成功消息
      */
+    public static AjaxResult success()
+    {
+        return new AjaxResult<>().setCode(RetCode.SUCCESS).setMsg("成功");
+    }
+
+    /**
+     * 返回成功消息
+     */
     public static <T> AjaxResult<T> success(T data)
     {
         return new AjaxResult<T>().setCode(RetCode.SUCCESS).setMsg("成功").setData(data);
@@ -115,6 +123,14 @@ public class AjaxResult<T> implements Serializable
     }
 
     /**
+     * 返回失败消息
+     */
+    public static <T> AjaxResult<T> error()
+    {
+        return new AjaxResult<T>().setCode(RetCode.FAIL).setMsg("失败");
+    }
+
+    /**
      * 返回消息
      */
     public static <T> AjaxResult<T> setResult(RetCode retCode, String message, T data)
diff --git a/src/main/java/com/huaheng/framework/web/domain/Ztree.java b/src/main/java/com/huaheng/framework/web/domain/Ztree.java
new file mode 100644
index 0000000..44fa103
--- /dev/null
+++ b/src/main/java/com/huaheng/framework/web/domain/Ztree.java
@@ -0,0 +1,104 @@
+package com.huaheng.framework.web.domain;
+
+import java.io.Serializable;
+
+/**
+ * Ztree树结构实体类
+ * @author Enzo Cotter
+ * @date 2020/6/30
+ */
+public class Ztree implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 节点ID */
+    private Long id;
+
+    /** 节点父ID */
+    private Long pId;
+
+    /** 节点名称 */
+    private String name;
+
+    /** 节点标题 */
+    private String title;
+
+    /** 是否勾选 */
+    private boolean checked = false;
+
+    /** 是否展开 */
+    private boolean open = false;
+
+    /** 是否能勾选 */
+    private boolean nocheck = false;
+
+    public Long getId()
+    {
+        return id;
+    }
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public Long getpId()
+    {
+        return pId;
+    }
+
+    public void setpId(Long pId)
+    {
+        this.pId = pId;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public String getTitle()
+    {
+        return title;
+    }
+
+    public void setTitle(String title)
+    {
+        this.title = title;
+    }
+
+    public boolean isChecked()
+    {
+        return checked;
+    }
+
+    public void setChecked(boolean checked)
+    {
+        this.checked = checked;
+    }
+
+    public boolean isOpen()
+    {
+        return open;
+    }
+
+    public void setOpen(boolean open)
+    {
+        this.open = open;
+    }
+
+    public boolean isNocheck()
+    {
+        return nocheck;
+    }
+
+    public void setNocheck(boolean nocheck)
+    {
+        this.nocheck = nocheck;
+    }
+}
diff --git a/src/main/java/com/huaheng/framework/web/page/TableDataInfo.java b/src/main/java/com/huaheng/framework/web/page/TableDataInfo.java
index 15b23ed..fbf982b 100644
--- a/src/main/java/com/huaheng/framework/web/page/TableDataInfo.java
+++ b/src/main/java/com/huaheng/framework/web/page/TableDataInfo.java
@@ -8,8 +8,7 @@ import java.util.List;
  * 
  * @author huaheng
  */
-public class TableDataInfo implements Serializable
-{
+public class TableDataInfo implements Serializable {
     private static final long serialVersionUID = 1L;
     /** 总记录数 */
     private long total;
diff --git a/src/main/java/com/huaheng/mobile/receipt/MobileBatchReceiptController.java b/src/main/java/com/huaheng/mobile/receipt/MobileBatchReceiptController.java
index 20aa9cc..04101a2 100644
--- a/src/main/java/com/huaheng/mobile/receipt/MobileBatchReceiptController.java
+++ b/src/main/java/com/huaheng/mobile/receipt/MobileBatchReceiptController.java
@@ -507,7 +507,7 @@ public class MobileBatchReceiptController {
         container.setWarehouseCode(ShiroUtils.getWarehouseCode());
         LambdaQueryWrapper lambdaQueryWrapper = Wrappers.lambdaQuery(container);
         container = containerService.getOne(lambdaQueryWrapper);
-        if(container==null) {
+        if(container == null) {
             return AjaxResult.error("容器不存在");
         }
         if (!StringUtils.isNotEmpty(container.getLocationCode()))  {
@@ -532,7 +532,7 @@ public class MobileBatchReceiptController {
         }
         //判断托盘是否已经存在任务
         LambdaQueryWrapper<TaskHeader> headerQueryWrapper = Wrappers.lambdaQuery();
-        headerQueryWrapper.ne(TaskHeader::getStatus, 100)
+        headerQueryWrapper.ne(TaskHeader::getStatus, QuantityConstant.TASK_STATUS_COMPLETED.intValue())
                 .eq(TaskHeader::getContainerCode, containerCode);
         Integer taskCount = taskHeaderService.count(headerQueryWrapper);
         if (taskCount != null && taskCount.intValue() > 0)  {
@@ -543,7 +543,7 @@ public class MobileBatchReceiptController {
         TaskHeader taskHeader = new TaskHeader();
         taskHeader.setWarehouseCode(loc.getWarehouseCode());
         taskHeader.setCompanyCode(companyCode);//货主
-        if(type == 200) {
+        if(type == QuantityConstant.TASK_TYPE_SUPPLEMENTRECEIPT.intValue()) {
             taskHeader.setInternalTaskType(QuantityConstant.TASK_INTENERTYPE_SHELF);
             taskHeader.setTaskType(QuantityConstant.TASK_TYPE_SUPPLEMENTRECEIPT);
         } else {
diff --git a/src/main/java/com/huaheng/pc/system/dict/controller/DictTypeController.java b/src/main/java/com/huaheng/pc/system/dict/controller/DictTypeController.java
index e6b9e89..832439e 100644
--- a/src/main/java/com/huaheng/pc/system/dict/controller/DictTypeController.java
+++ b/src/main/java/com/huaheng/pc/system/dict/controller/DictTypeController.java
@@ -2,6 +2,7 @@ package com.huaheng.pc.system.dict.controller;
 
 import java.util.List;
 
+import com.huaheng.framework.web.domain.Ztree;
 import com.huaheng.framework.web.service.DictService;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -142,4 +143,26 @@ public class DictTypeController extends BaseController {
         }
         return Integer.valueOf(uniqueFlag);
     }
+
+    /**
+     * 选择字典树
+     */
+    @GetMapping("/selectDictTree/{columnId}/{dictType}")
+    public String selectDeptTree(@PathVariable("columnId") Long columnId, @PathVariable("dictType") String dictType,
+                                 ModelMap mmap) {
+        mmap.put("columnId", columnId);
+        mmap.put("dict", dictTypeService.selectDictTypeByType(dictType));
+        return prefix + "/tree";
+    }
+
+    /**
+     * 加载字典列表树
+     */
+    @GetMapping("/treeData")
+    @ResponseBody
+    public List<Ztree> treeData() {
+        List<Ztree> ztrees = dictTypeService.selectDictTree(new DictType());
+        return ztrees;
+    }
+
 }
diff --git a/src/main/java/com/huaheng/pc/system/dict/mapper/DictTypeMapper.java b/src/main/java/com/huaheng/pc/system/dict/mapper/DictTypeMapper.java
index 718cdfe..44328c8 100644
--- a/src/main/java/com/huaheng/pc/system/dict/mapper/DictTypeMapper.java
+++ b/src/main/java/com/huaheng/pc/system/dict/mapper/DictTypeMapper.java
@@ -23,6 +23,14 @@ public interface DictTypeMapper
     public List<DictType> selectDictTypeList(DictType dictType);
 
     /**
+     * 根据字典类型查询信息
+     *
+     * @param dictType 字典类型
+     * @return 字典类型
+     */
+    public DictType selectDictTypeByType(String dictType);
+
+    /**
      * 根据所有字典类型
      * 
      * @return 字典类型集合信息
diff --git a/src/main/java/com/huaheng/pc/system/dict/service/DictTypeServiceImpl.java b/src/main/java/com/huaheng/pc/system/dict/service/DictTypeServiceImpl.java
index e9addcc..9a121ba 100644
--- a/src/main/java/com/huaheng/pc/system/dict/service/DictTypeServiceImpl.java
+++ b/src/main/java/com/huaheng/pc/system/dict/service/DictTypeServiceImpl.java
@@ -1,6 +1,7 @@
 package com.huaheng.pc.system.dict.service;
 
 import com.huaheng.common.exception.service.ServiceException;
+import com.huaheng.framework.web.domain.Ztree;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -46,6 +47,17 @@ public class DictTypeServiceImpl  implements IDictTypeService
     }
 
     /**
+     * 根据字典类型查询信息
+     *
+     * @param dictType 字典类型
+     * @return 字典类型
+     */
+    @Override
+    public DictType selectDictTypeByType(String dictType) {
+        return dictTypeMapper.selectDictTypeByType(dictType);
+    }
+
+    /**
      * 根据所有字典类型
      * 
      * @return 字典类型集合信息
@@ -204,4 +216,33 @@ public class DictTypeServiceImpl  implements IDictTypeService
         }
         return true;
     }
+
+    /**
+     * 查询字典类型树
+     *
+     * @param dictType 字典类型
+     * @return 所有字典类型
+     */
+    @Override
+    public List<Ztree> selectDictTree(DictType dictType) {
+        List<Ztree> ztrees = new ArrayList<Ztree>();
+        List<DictType> dictList = dictTypeMapper.selectDictTypeList(dictType);
+        for (DictType dict : dictList) {
+            if (dict.getEnable()) {
+                Ztree ztree = new Ztree();
+                ztree.setId(Long.valueOf(dict.getId()));
+                ztree.setName(transDictName(dict));
+                ztree.setTitle(dict.getDictType());
+                ztrees.add(ztree);
+            }
+        }
+        return ztrees;
+    }
+
+    public String transDictName(DictType dictType) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("(" + dictType.getDictName() + ")");
+        sb.append("&nbsp;&nbsp;&nbsp;" + dictType.getDictType());
+        return sb.toString();
+    }
 }
diff --git a/src/main/java/com/huaheng/pc/system/dict/service/IDictTypeService.java b/src/main/java/com/huaheng/pc/system/dict/service/IDictTypeService.java
index 8b8a44c..fd866d2 100644
--- a/src/main/java/com/huaheng/pc/system/dict/service/IDictTypeService.java
+++ b/src/main/java/com/huaheng/pc/system/dict/service/IDictTypeService.java
@@ -1,5 +1,6 @@
 package com.huaheng.pc.system.dict.service;
 
+import com.huaheng.framework.web.domain.Ztree;
 import com.huaheng.pc.system.dict.domain.DictType;
 import org.apache.ibatis.annotations.Param;
 
@@ -22,6 +23,14 @@ public interface IDictTypeService
     public List<DictType> selectDictTypeList(DictType dictType);
 
     /**
+     * 根据字典类型查询信息
+     *
+     * @param dictType 字典类型
+     * @return 字典类型
+     */
+    public DictType selectDictTypeByType(String dictType);
+
+    /**
      * 根据所有字典类型
      * 
      * @return 字典类型集合信息
@@ -99,4 +108,12 @@ public interface IDictTypeService
 
     //复制字典数据
     Boolean dictTypeCopy(String code,String newCode);
+
+    /**
+     * 查询字典类型树
+     *
+     * @param dictType 字典类型
+     * @return 所有字典类型
+     */
+    public List<Ztree> selectDictTree(DictType dictType);
 }
diff --git a/src/main/java/com/huaheng/pc/task/taskHeader/service/TaskHeaderServiceImpl.java b/src/main/java/com/huaheng/pc/task/taskHeader/service/TaskHeaderServiceImpl.java
index 9b2f72d..cd8e1f0 100644
--- a/src/main/java/com/huaheng/pc/task/taskHeader/service/TaskHeaderServiceImpl.java
+++ b/src/main/java/com/huaheng/pc/task/taskHeader/service/TaskHeaderServiceImpl.java
@@ -677,7 +677,7 @@ public class TaskHeaderServiceImpl extends ServiceImpl<TaskHeaderMapper, TaskHea
         if (task.getInternalTaskType().equals(QuantityConstant.TASK_INTENERTYPE_SHELF) ) {
             //入库任务
             this.completeReceiptTask(task);
-//            combineInventory(task);
+            combineInventory(task);
         }
         if (task.getInternalTaskType().equals(QuantityConstant.TASK_INTENERTYPE_PICKING) &&
                 (task.getTaskType().equals(QuantityConstant.TASK_TYPE_WHOLESHIPMENT) || task.getTaskType().equals(QuantityConstant.TASK_TYPE_SORTINGSHIPMENT))) {
@@ -719,7 +719,7 @@ public class TaskHeaderServiceImpl extends ServiceImpl<TaskHeaderMapper, TaskHea
         }
         LambdaQueryWrapper<InventoryHeader> inventoryHeaderErapper = Wrappers.lambdaQuery();
         inventoryHeaderErapper.eq(InventoryHeader::getWarehouseCode, ShiroUtils.getWarehouseCode())
-                .eq(InventoryHeader::getLocationCode, task.getFromLocation())
+                .eq(InventoryHeader::getLocationCode, task.getToLocation())
                 .eq(InventoryHeader::getContainerCode, task.getContainerCode());
         InventoryHeader header = inventoryHeaderService.getOne(inventoryHeaderErapper);
 
@@ -747,12 +747,12 @@ public class TaskHeaderServiceImpl extends ServiceImpl<TaskHeaderMapper, TaskHea
             //将未完成的任务数量更新到库存表
             if (DataUtils.getInteger(map.get("status")) < QuantityConstant.TASK_STATUS_COMPLETED) {
 
-
                 LambdaQueryWrapper<InventoryDetail> inventory = Wrappers.lambdaQuery();
                 inventory.eq(InventoryDetail::getWarehouseCode, ShiroUtils.getWarehouseCode())
-                        .eq(InventoryDetail::getLocationCode, task.getFromLocation())
+                        .eq(InventoryDetail::getLocationCode, task.getToLocation())
                         .eq(InventoryDetail::getMaterialCode, DataUtils.getString(map.get("materialCode")))
-                        .eq(InventoryDetail::getContainerCode, DataUtils.getString(map.get("containerCode")));
+                        .eq(InventoryDetail::getContainerCode, DataUtils.getString(map.get("containerCode")))
+                        .eq(InventoryDetail::getContainerCode, DataUtils.getString(map.get("batch")));
                 InventoryDetail detail = inventoryDetailService.getOne(inventory);
                 if (detail == null) {
                     //库存明细添加
@@ -892,21 +892,41 @@ public class TaskHeaderServiceImpl extends ServiceImpl<TaskHeaderMapper, TaskHea
         return AjaxResult.success("完成入库任务");
     }
 
-//    private void combineInventory(TaskHeader task) {
-//        LambdaQueryWrapper<InventoryHeader> inventoryHeaderErapper = Wrappers.lambdaQuery();
-//        inventoryHeaderErapper.eq(InventoryHeader::getWarehouseCode, ShiroUtils.getWarehouseCode())
-//                .eq(InventoryHeader::getLocationCode, task.getFromLocation())
-//                .eq(InventoryHeader::getContainerCode, task.getContainerCode());
-//        InventoryHeader header = inventoryHeaderService.getOne(inventoryHeaderErapper);
-//        if(header != null) {
-//            LambdaQueryWrapper<InventoryDetail> inventory = Wrappers.lambdaQuery();
-//            inventory.eq(InventoryDetail::getWarehouseCode, ShiroUtils.getWarehouseCode())
-//                    .eq(InventoryDetail::getLocationCode, task.getFromLocation())
-//                    .eq(InventoryDetail::getMaterialCode, DataUtils.getString(map.get("materialCode")))
-//                    .eq(InventoryDetail::getContainerCode, DataUtils.getString(map.get("containerCode")));
-//            List<InventoryDetail> detail = inventoryDetailService.list(inventory);
-//        }
-//    }
+    @Transactional(rollbackFor = Exception.class)
+    public void combineInventory(TaskHeader task) {
+        LambdaQueryWrapper<InventoryHeader> inventoryHeaderErapper = Wrappers.lambdaQuery();
+        inventoryHeaderErapper.eq(InventoryHeader::getWarehouseCode, ShiroUtils.getWarehouseCode())
+                .eq(InventoryHeader::getLocationCode, task.getToLocation())
+                .eq(InventoryHeader::getContainerCode, task.getContainerCode());
+        InventoryHeader header = inventoryHeaderService.getOne(inventoryHeaderErapper);
+        if(header != null) {
+            LambdaQueryWrapper<InventoryDetail> inventory = Wrappers.lambdaQuery();
+            inventory.eq(InventoryDetail::getWarehouseCode, ShiroUtils.getWarehouseCode())
+                    .eq(InventoryDetail::getLocationCode, task.getFromLocation())
+                    .eq(InventoryDetail::getContainerCode, task.getContainerCode());
+            List<InventoryDetail> inventoryDetailList = inventoryDetailService.list(inventory);
+            for(int i=0; i < inventoryDetailList.size() -1; i++) {
+                for(int j = inventoryDetailList.size() - 1; j > i; j--) {
+                    if(inventoryDetailList.get(i).getMaterialCode().equals(inventoryDetailList.get(j).getMaterialCode())) {
+                        BigDecimal totalQty = inventoryDetailList.get(i).getQty().add(inventoryDetailList.get(j).getQty());
+                        inventoryDetailList.get(i).setQty(totalQty);
+                        inventoryDetailList.remove(j);
+                        LambdaQueryWrapper<InventoryDetail> wrapper = Wrappers.lambdaQuery();
+                        wrapper.eq(InventoryDetail::getId, inventoryDetailList.get(i).getId());
+                        inventoryDetailService.update(inventoryDetailList.get(i), wrapper);
+                    }
+                }
+            }
+            BigDecimal totalQty = new BigDecimal(0);
+            for(InventoryDetail inventoryDetail : inventoryDetailList) {
+                 totalQty = totalQty.add(inventoryDetail.getQty());
+            }
+            header.setTotalQty(totalQty);
+            LambdaQueryWrapper<InventoryHeader> wrapper = Wrappers.lambdaQuery();
+            wrapper.eq(InventoryHeader::getId, header.getId());
+            inventoryHeaderService.update(header, wrapper);
+        }
+    }
 
     /**
      * 移动端创建入库任务
diff --git a/src/main/java/com/huaheng/pc/tool/gen/controller/GenController.java b/src/main/java/com/huaheng/pc/tool/gen/controller/GenController.java
index 2389781..0c44f56 100644
--- a/src/main/java/com/huaheng/pc/tool/gen/controller/GenController.java
+++ b/src/main/java/com/huaheng/pc/tool/gen/controller/GenController.java
@@ -1,89 +1,201 @@
 package com.huaheng.pc.tool.gen.controller;
 
-import java.io.IOException;
-import java.util.List;
-import javax.servlet.http.HttpServletResponse;
+import com.alibaba.fastjson.JSON;
+import com.huaheng.common.support.Convert;
+import com.huaheng.common.utils.StringUtils;
+import com.huaheng.common.utils.security.ShiroUtils;
+import com.huaheng.framework.web.controller.BaseController;
+import com.huaheng.framework.web.domain.AjaxResult;
+import com.huaheng.framework.web.page.TableDataInfo;
+import com.huaheng.pc.tool.gen.domain.CxSelect;
+import com.huaheng.pc.tool.gen.domain.GenTable;
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+import com.huaheng.pc.tool.gen.service.IGenTableColumnService;
+import com.huaheng.pc.tool.gen.service.IGenTableService;
 import org.apache.commons.io.IOUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import com.huaheng.common.support.Convert;
-import com.huaheng.framework.aspectj.lang.annotation.Log;
-import com.huaheng.framework.aspectj.lang.constant.BusinessType;
-import com.huaheng.framework.web.controller.BaseController;
-import com.huaheng.framework.web.page.TableDataInfo;
-import com.huaheng.pc.tool.gen.domain.TableInfo;
-import com.huaheng.pc.tool.gen.service.IGenService;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 代码生成 操作处理
- * 
- * @author huaheng
+ *
+ * @author ruoyi
  */
 @Controller
 @RequestMapping("/tool/gen")
-public class GenController extends BaseController
-{
+public class GenController extends BaseController {
     private String prefix = "tool/gen";
 
     @Autowired
-    private IGenService genService;
+    private IGenTableService genTableService;
+
+    @Autowired
+    private IGenTableColumnService genTableColumnService;
 
     @RequiresPermissions("tool:gen:view")
     @GetMapping()
-    public String gen()
-    {
+    public String gen() {
         return prefix + "/gen";
     }
 
+    /**
+     * 查询代码生成列表
+     */
     @RequiresPermissions("tool:gen:list")
     @PostMapping("/list")
     @ResponseBody
-    public TableDataInfo list(TableInfo tableInfo)
-    {
+    public TableDataInfo genList(GenTable genTable) {
+        startPage();
+        List<GenTable> list = genTableService.selectGenTableList(genTable);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询数据库列表
+     */
+    @RequiresPermissions("tool:gen:list")
+    @PostMapping("/db/list")
+    @ResponseBody
+    public TableDataInfo dataList(GenTable genTable) {
         startPage();
-        List<TableInfo> list = genService.selectTableList(tableInfo);
+        List<GenTable> list = genTableService.selectDbTableList(genTable);
         return getDataTable(list);
     }
 
     /**
+     * 查询数据表字段列表
+     */
+    @RequiresPermissions("tool:gen:list")
+    @PostMapping("/column/list")
+    @ResponseBody
+    public TableDataInfo columnList(GenTableColumn genTableColumn) {
+        TableDataInfo dataInfo = new TableDataInfo();
+        List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(genTableColumn);
+        dataInfo.setData(list);
+        dataInfo.setTotal(list.size());
+        dataInfo.setCode(200);
+        return dataInfo;
+    }
+
+    /**
+     * 导入表结构
+     */
+    @RequiresPermissions("tool:gen:list")
+    @GetMapping("/importTable")
+    public String importTable() {
+        return prefix + "/importTable";
+    }
+
+    /**
+     * 导入表结构(保存)
+     */
+    @RequiresPermissions("tool:gen:list")
+    @PostMapping("/importTable")
+    @ResponseBody
+    public AjaxResult importTableSave(String tables) {
+        String[] tableNames = Convert.toStrArray(tables);
+        // 查询表信息
+        List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
+        String operName = ShiroUtils.getLoginName();
+        genTableService.importGenTable(tableList, operName);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 修改代码生成业务
+     */
+    @GetMapping("/edit/{tableId}")
+    public String edit(@PathVariable("tableId") Long tableId, ModelMap mmap) {
+        GenTable table = genTableService.selectGenTableById(tableId);
+        List<GenTable> genTables = genTableService.selectGenTableAll();
+        List<CxSelect> cxSelect = new ArrayList<CxSelect>();
+        for (GenTable genTable : genTables) {
+            if (!StringUtils.equals(table.getTableName(), genTable.getTableName())) {
+                CxSelect cxTable = new CxSelect(genTable.getTableName(), genTable.getTableName() + ':' + genTable.getTableComment());
+                List<CxSelect> cxColumns = new ArrayList<CxSelect>();
+                for (GenTableColumn tableColumn : genTable.getColumns()) {
+                    cxColumns.add(new CxSelect(tableColumn.getColumnName(), tableColumn.getColumnName() + ':' + tableColumn.getColumnComment()));
+                }
+                cxTable.setS(cxColumns);
+                cxSelect.add(cxTable);
+            }
+        }
+        mmap.put("table", table);
+        mmap.put("data", JSON.toJSON(cxSelect));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存代码生成业务
+     */
+    @RequiresPermissions("tool:gen:edit")
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated GenTable genTable) {
+        genTableService.validateEdit(genTable);
+        genTableService.updateGenTable(genTable);
+        return AjaxResult.success();
+    }
+
+    @RequiresPermissions("tool:gen:remove")
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids) {
+        genTableService.deleteGenTableByIds(ids);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 预览代码
+     */
+    @RequiresPermissions("tool:gen:preview")
+    @GetMapping("/preview/{tableId}")
+    @ResponseBody
+    public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException {
+        Map<String, String> dataMap = genTableService.previewCode(tableId);
+        return AjaxResult.success(dataMap);
+    }
+
+    /**
      * 生成代码
      */
     @RequiresPermissions("tool:gen:code")
-    @Log(title = "系统工具-代码生成", operating = "生成代码", action = BusinessType.GENCODE)
     @GetMapping("/genCode/{tableName}")
-    public void genCode(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException
-    {
-        byte[] data = genService.generatorCode(tableName);
-        response.reset();
-        response.setHeader("Content-Disposition", "attachment; filename=\"huaheng.zip\"");
-        response.addHeader("Content-Length", "" + data.length);
-        response.setContentType("application/octet-stream; charset=UTF-8");
-
-        IOUtils.write(data, response.getOutputStream());
+    public void genCode(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException {
+        byte[] data = genTableService.generatorCode(tableName);
+        genCode(response, data);
     }
 
     /**
      * 批量生成代码
      */
     @RequiresPermissions("tool:gen:code")
-    @Log(title = "系统工具-代码生成", operating = "批量代码生成", action = BusinessType.GENCODE)
     @GetMapping("/batchGenCode")
     @ResponseBody
-    public void batchGenCode(HttpServletResponse response, String tables) throws IOException
-    {
+    public void batchGenCode(HttpServletResponse response, String tables) throws IOException {
         String[] tableNames = Convert.toStrArray(tables);
-        byte[] data = genService.generatorCode(tableNames);
+        byte[] data = genTableService.generatorCode(tableNames);
+        genCode(response, data);
+    }
+
+    /**
+     * 生成zip文件
+     */
+    private void genCode(HttpServletResponse response, byte[] data) throws IOException {
         response.reset();
         response.setHeader("Content-Disposition", "attachment; filename=\"huaheng.zip\"");
         response.addHeader("Content-Length", "" + data.length);
         response.setContentType("application/octet-stream; charset=UTF-8");
-
         IOUtils.write(data, response.getOutputStream());
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/huaheng/pc/tool/gen/domain/ColumnInfo.java b/src/main/java/com/huaheng/pc/tool/gen/domain/ColumnInfo.java
deleted file mode 100644
index 91f802c..0000000
--- a/src/main/java/com/huaheng/pc/tool/gen/domain/ColumnInfo.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package com.huaheng.pc.tool.gen.domain;
-
-/**
- * ry数据库表列信息
- * 
- * @author huaheng
- */
-public class ColumnInfo
-{
-    /** 表名称 */
-    private String tableName;
-    /** 字段名称 */
-    private String columnName;
-    /** 字段类型 */
-    private String dataType;
-    /** 字段是否可为null */
-    private String isNullable;
-    /** 列默认值 */
-    private String columnDefault;
-    /** 列描述 */
-    private String columnComment;
-    /** Java属性类型 */
-    private String attrType;
-    /** Java属性名称(第一个字母大写),如:userName => UserName */
-    private String attrName;
-    /** Java属性名称(第一个字母小写),如:userName => userName */
-    private String attrname;
-
-    public String getTableName() {
-        return tableName;
-    }
-
-    public void setTableName(String tableName) {
-        this.tableName = tableName;
-    }
-
-    public String getIsNullable() {
-        return isNullable;
-    }
-
-    public void setIsNullable(String isNullable) {
-        this.isNullable = isNullable;
-    }
-
-    public String getColumnDefault() {
-        return columnDefault;
-    }
-
-    public void setColumnDefault(String columnDefault) {
-        this.columnDefault = columnDefault;
-    }
-
-    public String getColumnName()
-    {
-        return columnName;
-    }
-
-    public void setColumnName(String columnName)
-    {
-        this.columnName = columnName;
-    }
-
-    public String getDataType()
-    {
-        return dataType;
-    }
-
-    public void setDataType(String dataType)
-    {
-        this.dataType = dataType;
-    }
-
-    public String getColumnComment()
-    {
-        return columnComment;
-    }
-
-    public void setColumnComment(String columnComment)
-    {
-        this.columnComment = columnComment;
-    }
-
-    public String getAttrName()
-    {
-        return attrName;
-    }
-
-    public void setAttrName(String attrName)
-    {
-        this.attrName = attrName;
-    }
-
-    public String getAttrname()
-    {
-        return attrname;
-    }
-
-    public void setAttrname(String attrname)
-    {
-        this.attrname = attrname;
-    }
-
-    public String getAttrType()
-    {
-        return attrType;
-    }
-
-    public void setAttrType(String attrType)
-    {
-        this.attrType = attrType;
-    }
-
-}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/domain/CxSelect.java b/src/main/java/com/huaheng/pc/tool/gen/domain/CxSelect.java
new file mode 100644
index 0000000..ba1e1f8
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/domain/CxSelect.java
@@ -0,0 +1,58 @@
+package com.huaheng.pc.tool.gen.domain;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * CxSelect树结构实体类
+ */
+public class CxSelect implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据值字段名称
+     */
+    private String v;
+
+    /**
+     * 数据标题字段名称
+     */
+    private String n;
+
+    /**
+     * 子集数据字段名称
+     */
+    private List<CxSelect> s;
+
+    public CxSelect() {
+    }
+
+    public CxSelect(String v, String n) {
+        this.v = v;
+        this.n = n;
+    }
+
+    public List<CxSelect> getS() {
+        return s;
+    }
+
+    public void setN(String n) {
+        this.n = n;
+    }
+
+    public String getN() {
+        return n;
+    }
+
+    public void setS(List<CxSelect> s) {
+        this.s = s;
+    }
+
+    public String getV() {
+        return v;
+    }
+
+    public void setV(String v) {
+        this.v = v;
+    }
+}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/domain/GenTable.java b/src/main/java/com/huaheng/pc/tool/gen/domain/GenTable.java
new file mode 100644
index 0000000..a26bfde
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/domain/GenTable.java
@@ -0,0 +1,311 @@
+package com.huaheng.pc.tool.gen.domain;
+
+import java.util.List;
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+
+import com.huaheng.common.constant.GenConstants;
+import com.huaheng.common.utils.StringUtils;
+import com.huaheng.framework.web.domain.BaseEntity;
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+import org.apache.commons.lang3.ArrayUtils;
+
+
+/**
+ * 业务表 gen_table
+ */
+public class GenTable extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 编号
+     */
+    private Long tableId;
+
+    /**
+     * 表名称
+     */
+    @NotBlank(message = "表名称不能为空")
+    private String tableName;
+
+    /**
+     * 表描述
+     */
+    @NotBlank(message = "表描述不能为空")
+    private String tableComment;
+
+    /**
+     * 关联父表的表名
+     */
+    private String subTableName;
+
+    /**
+     * 本表关联父表的外键名
+     */
+    private String subTableFkName;
+
+    /**
+     * 实体类名称(首字母大写)
+     */
+    @NotBlank(message = "实体类名称不能为空")
+    private String className;
+
+    /**
+     * 使用的模板(crud单表操作 tree树表操作 sub主子表操作)
+     */
+    private String tplCategory;
+
+    /**
+     * 生成包路径
+     */
+    @NotBlank(message = "生成包路径不能为空")
+    private String packageName;
+
+    /**
+     * 生成模块名
+     */
+    @NotBlank(message = "生成模块名不能为空")
+    private String moduleName;
+
+    /**
+     * 生成业务名
+     */
+    @NotBlank(message = "生成业务名不能为空")
+    private String businessName;
+
+    /**
+     * 生成功能名
+     */
+    @NotBlank(message = "生成功能名不能为空")
+    private String functionName;
+
+    /**
+     * 生成作者
+     */
+    @NotBlank(message = "作者不能为空")
+    private String functionAuthor;
+
+    /**
+     * 主键信息
+     */
+    private GenTableColumn pkColumn;
+
+    /**
+     * 子表信息
+     */
+    private GenTable subTable;
+
+    /**
+     * 表列信息
+     */
+    @Valid
+    private List<GenTableColumn> columns;
+
+    /**
+     * 其它生成选项
+     */
+    private String options;
+
+    /**
+     * 树编码字段
+     */
+    private String treeCode;
+
+    /**
+     * 树父编码字段
+     */
+    private String treeParentCode;
+
+    /**
+     * 树名称字段
+     */
+    private String treeName;
+
+    public Long getTableId() {
+        return tableId;
+    }
+
+    public void setTableId(Long tableId) {
+        this.tableId = tableId;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public String getTableComment() {
+        return tableComment;
+    }
+
+    public void setTableComment(String tableComment) {
+        this.tableComment = tableComment;
+    }
+
+    public String getSubTableName() {
+        return subTableName;
+    }
+
+    public void setSubTableName(String subTableName) {
+        this.subTableName = subTableName;
+    }
+
+    public String getSubTableFkName() {
+        return subTableFkName;
+    }
+
+    public void setSubTableFkName(String subTableFkName) {
+        this.subTableFkName = subTableFkName;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public void setClassName(String className) {
+        this.className = className;
+    }
+
+    public String getTplCategory() {
+        return tplCategory;
+    }
+
+    public void setTplCategory(String tplCategory) {
+        this.tplCategory = tplCategory;
+    }
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public void setPackageName(String packageName) {
+        this.packageName = packageName;
+    }
+
+    public String getModuleName() {
+        return moduleName;
+    }
+
+    public void setModuleName(String moduleName) {
+        this.moduleName = moduleName;
+    }
+
+    public String getBusinessName() {
+        return businessName;
+    }
+
+    public void setBusinessName(String businessName) {
+        this.businessName = businessName;
+    }
+
+    public String getFunctionName() {
+        return functionName;
+    }
+
+    public void setFunctionName(String functionName) {
+        this.functionName = functionName;
+    }
+
+    public String getFunctionAuthor() {
+        return functionAuthor;
+    }
+
+    public void setFunctionAuthor(String functionAuthor) {
+        this.functionAuthor = functionAuthor;
+    }
+
+    public GenTableColumn getPkColumn() {
+        return pkColumn;
+    }
+
+    public void setPkColumn(GenTableColumn pkColumn) {
+        this.pkColumn = pkColumn;
+    }
+
+    public GenTable getSubTable() {
+        return subTable;
+    }
+
+    public void setSubTable(GenTable subTable) {
+        this.subTable = subTable;
+    }
+
+    public List<GenTableColumn> getColumns() {
+        return columns;
+    }
+
+    public void setColumns(List<GenTableColumn> columns) {
+        this.columns = columns;
+    }
+
+    public String getOptions() {
+        return options;
+    }
+
+    public void setOptions(String options) {
+        this.options = options;
+    }
+
+    public String getTreeCode() {
+        return treeCode;
+    }
+
+    public void setTreeCode(String treeCode) {
+        this.treeCode = treeCode;
+    }
+
+    public String getTreeParentCode() {
+        return treeParentCode;
+    }
+
+    public void setTreeParentCode(String treeParentCode) {
+        this.treeParentCode = treeParentCode;
+    }
+
+    public String getTreeName() {
+        return treeName;
+    }
+
+    public void setTreeName(String treeName) {
+        this.treeName = treeName;
+    }
+
+    public boolean isSub() {
+        return isSub(this.tplCategory);
+    }
+
+    public static boolean isSub(String tplCategory) {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);
+    }
+
+    public boolean isTree() {
+        return isTree(this.tplCategory);
+    }
+
+    public static boolean isTree(String tplCategory) {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
+    }
+
+    public boolean isCrud() {
+        return isCrud(this.tplCategory);
+    }
+
+    public static boolean isCrud(String tplCategory) {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
+    }
+
+    public boolean isSuperColumn(String javaField) {
+        return isSuperColumn(this.tplCategory, javaField);
+    }
+
+    public static boolean isSuperColumn(String tplCategory, String javaField) {
+        if (isTree(tplCategory)) {
+            return StringUtils.equalsAnyIgnoreCase(javaField,
+                    ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY));
+        }
+        return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/huaheng/pc/tool/gen/domain/GenTableColumn.java b/src/main/java/com/huaheng/pc/tool/gen/domain/GenTableColumn.java
new file mode 100644
index 0000000..947e599
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/domain/GenTableColumn.java
@@ -0,0 +1,346 @@
+package com.huaheng.pc.tool.gen.domain;
+
+import com.huaheng.common.utils.StringUtils;
+import com.huaheng.framework.web.domain.BaseEntity;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 代码生成业务字段表 gen_table_column
+ */
+public class GenTableColumn extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 编号
+     */
+    private Long columnId;
+
+    /**
+     * 归属表编号
+     */
+    private Long tableId;
+
+    /**
+     * 列名称
+     */
+    private String columnName;
+
+    /**
+     * 列描述
+     */
+    private String columnComment;
+
+    /**
+     * 列类型
+     */
+    private String columnType;
+
+    /**
+     * JAVA类型
+     */
+    private String javaType;
+
+    /**
+     * JAVA字段名
+     */
+    @NotBlank(message = "Java属性不能为空")
+    private String javaField;
+
+    /**
+     * 是否主键(1是)
+     */
+    private String isPk;
+
+    /**
+     * 是否自增(1是)
+     */
+    private String isIncrement;
+
+    /**
+     * 是否必填(1是)
+     */
+    private String isRequired;
+
+    /**
+     * 是否为插入字段(1是)
+     */
+    private String isInsert;
+
+    /**
+     * 是否编辑字段(1是)
+     */
+    private String isEdit;
+
+    /**
+     * 是否列表字段(1是)
+     */
+    private String isList;
+
+    /**
+     * 是否查询字段(1是)
+     */
+    private String isQuery;
+
+    /**
+     * 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围)
+     */
+    private String queryType;
+
+    /**
+     * 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件)
+     */
+    private String htmlType;
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    public void setColumnId(Long columnId) {
+        this.columnId = columnId;
+    }
+
+    public Long getColumnId() {
+        return columnId;
+    }
+
+    public void setTableId(Long tableId) {
+        this.tableId = tableId;
+    }
+
+    public Long getTableId() {
+        return tableId;
+    }
+
+    public void setColumnName(String columnName) {
+        this.columnName = columnName;
+    }
+
+    public String getColumnName() {
+        return columnName;
+    }
+
+    public void setColumnComment(String columnComment) {
+        this.columnComment = columnComment;
+    }
+
+    public String getColumnComment() {
+        return columnComment;
+    }
+
+    public void setColumnType(String columnType) {
+        this.columnType = columnType;
+    }
+
+    public String getColumnType() {
+        return columnType;
+    }
+
+    public void setJavaType(String javaType) {
+        this.javaType = javaType;
+    }
+
+    public String getJavaType() {
+        return javaType;
+    }
+
+    public void setJavaField(String javaField) {
+        this.javaField = javaField;
+    }
+
+    public String getJavaField() {
+        return javaField;
+    }
+
+    public String getCapJavaField() {
+        return StringUtils.capitalize(javaField);
+    }
+
+    public void setIsPk(String isPk) {
+        this.isPk = isPk;
+    }
+
+    public String getIsPk() {
+        return isPk;
+    }
+
+    public boolean isPk() {
+        return isPk(this.isPk);
+    }
+
+    public boolean isPk(String isPk) {
+        return isPk != null && StringUtils.equals("1", isPk);
+    }
+
+    public String getIsIncrement() {
+        return isIncrement;
+    }
+
+    public void setIsIncrement(String isIncrement) {
+        this.isIncrement = isIncrement;
+    }
+
+    public boolean isIncrement() {
+        return isIncrement(this.isIncrement);
+    }
+
+    public boolean isIncrement(String isIncrement) {
+        return isIncrement != null && StringUtils.equals("1", isIncrement);
+    }
+
+    public void setIsRequired(String isRequired) {
+        this.isRequired = isRequired;
+    }
+
+    public String getIsRequired() {
+        return isRequired;
+    }
+
+    public boolean isRequired() {
+        return isRequired(this.isRequired);
+    }
+
+    public boolean isRequired(String isRequired) {
+        return isRequired != null && StringUtils.equals("1", isRequired);
+    }
+
+    public void setIsInsert(String isInsert) {
+        this.isInsert = isInsert;
+    }
+
+    public String getIsInsert() {
+        return isInsert;
+    }
+
+    public boolean isInsert() {
+        return isInsert(this.isInsert);
+    }
+
+    public boolean isInsert(String isInsert) {
+        return isInsert != null && StringUtils.equals("1", isInsert);
+    }
+
+    public void setIsEdit(String isEdit) {
+        this.isEdit = isEdit;
+    }
+
+    public String getIsEdit() {
+        return isEdit;
+    }
+
+    public boolean isEdit() {
+        return isInsert(this.isEdit);
+    }
+
+    public boolean isEdit(String isEdit) {
+        return isEdit != null && StringUtils.equals("1", isEdit);
+    }
+
+    public void setIsList(String isList) {
+        this.isList = isList;
+    }
+
+    public String getIsList() {
+        return isList;
+    }
+
+    public boolean isList() {
+        return isList(this.isList);
+    }
+
+    public boolean isList(String isList) {
+        return isList != null && StringUtils.equals("1", isList);
+    }
+
+    public void setIsQuery(String isQuery) {
+        this.isQuery = isQuery;
+    }
+
+    public String getIsQuery() {
+        return isQuery;
+    }
+
+    public boolean isQuery() {
+        return isQuery(this.isQuery);
+    }
+
+    public boolean isQuery(String isQuery) {
+        return isQuery != null && StringUtils.equals("1", isQuery);
+    }
+
+    public void setQueryType(String queryType) {
+        this.queryType = queryType;
+    }
+
+    public String getQueryType() {
+        return queryType;
+    }
+
+    public String getHtmlType() {
+        return htmlType;
+    }
+
+    public void setHtmlType(String htmlType) {
+        this.htmlType = htmlType;
+    }
+
+    public void setDictType(String dictType) {
+        this.dictType = dictType;
+    }
+
+    public String getDictType() {
+        return dictType;
+    }
+
+    public void setSort(Integer sort) {
+        this.sort = sort;
+    }
+
+    public Integer getSort() {
+        return sort;
+    }
+
+    public boolean isSuperColumn() {
+        return isSuperColumn(this.javaField);
+    }
+
+    public static boolean isSuperColumn(String javaField) {
+        return StringUtils.equalsAnyIgnoreCase(javaField,
+                // BaseEntity
+                "createBy", "createTime", "updateBy", "updateTime", "remark",
+                // TreeEntity
+                "parentName", "parentId", "orderNum", "ancestors");
+    }
+
+    public boolean isUsableColumn() {
+        return isUsableColumn(javaField);
+    }
+
+    public static boolean isUsableColumn(String javaField) {
+        // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单
+        return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum");
+    }
+
+    public String readConverterExp() {
+        String remarks = StringUtils.substringBetween(this.columnComment, "(", ")");
+        StringBuffer sb = new StringBuffer();
+        if (StringUtils.isNotEmpty(remarks)) {
+            for (String value : remarks.split(" ")) {
+                if (StringUtils.isNotEmpty(value)) {
+                    Object startStr = value.subSequence(0, 1);
+                    String endStr = value.substring(1);
+                    sb.append("").append(startStr).append("=").append(endStr).append(",");
+                }
+            }
+            return sb.deleteCharAt(sb.length() - 1).toString();
+        } else {
+            return this.columnComment;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/huaheng/pc/tool/gen/domain/TableInfo.java b/src/main/java/com/huaheng/pc/tool/gen/domain/TableInfo.java
deleted file mode 100644
index 4d88671..0000000
--- a/src/main/java/com/huaheng/pc/tool/gen/domain/TableInfo.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package com.huaheng.pc.tool.gen.domain;
-
-import java.util.List;
-import com.huaheng.common.utils.StringUtils;
-import com.huaheng.framework.web.domain.BaseEntity;
-
-/**
- * ry 数据库表
- * 
- * @author huaheng
- */
-public class TableInfo extends BaseEntity
-{
-    private static final long serialVersionUID = 1L;
-    
-    /** 表名称 */
-    private String tableName;
-
-    /** 表描述 */
-    private String tableComment;
-
-    /** 表的主键列信息 */
-    private ColumnInfo primaryKey;
-
-    /** 表的列名(不包含主键) */
-    private List<ColumnInfo> columns;
-
-    /** 类名(第一个字母大写) */
-    private String className;
-
-    /** 类名(第一个字母小写) */
-    private String classname;
-
-    public String getTableName()
-    {
-        return tableName;
-    }
-
-    public void setTableName(String tableName)
-    {
-        this.tableName = tableName;
-    }
-
-    public String getTableComment()
-    {
-        return tableComment;
-    }
-
-    public void setTableComment(String tableComment)
-    {
-        this.tableComment = tableComment;
-    }
-
-    public List<ColumnInfo> getColumns()
-    {
-        return columns;
-    }
-
-    public ColumnInfo getColumnsLast()
-    {
-        ColumnInfo columnInfo = null;
-        if (StringUtils.isNotNull(columns) && columns.size() > 0)
-        {
-            columnInfo = columns.get(0);
-        }
-        return columnInfo;
-    }
-
-    public void setColumns(List<ColumnInfo> columns)
-    {
-        this.columns = columns;
-    }
-
-    public String getClassName()
-    {
-        return className;
-    }
-
-    public void setClassName(String className)
-    {
-        this.className = className;
-    }
-
-    public String getClassname()
-    {
-        return classname;
-    }
-
-    public void setClassname(String classname)
-    {
-        this.classname = classname;
-    }
-
-    public ColumnInfo getPrimaryKey()
-    {
-        return primaryKey;
-    }
-
-    public void setPrimaryKey(ColumnInfo primaryKey)
-    {
-        this.primaryKey = primaryKey;
-    }
-
-    @Override
-    public String toString()
-    {
-        return "TableInfo [tableName=" + tableName + ", tableComment=" + tableComment + ", primaryKey=" + primaryKey
-                + ", columns=" + columns + ", className=" + className + ", classname=" + classname + "]";
-    }
-
-}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/mapper/GenMapper.java b/src/main/java/com/huaheng/pc/tool/gen/mapper/GenMapper.java
deleted file mode 100644
index e5f158f..0000000
--- a/src/main/java/com/huaheng/pc/tool/gen/mapper/GenMapper.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.huaheng.pc.tool.gen.mapper;
-
-import java.util.List;
-import com.huaheng.pc.tool.gen.domain.ColumnInfo;
-import com.huaheng.pc.tool.gen.domain.TableInfo;
-import org.springframework.stereotype.Service;
-
-/**
- * 代码生成 数据层
- * 
- * @author huaheng
- */
-@Service
-public interface GenMapper
-{
-    /**
-     * 查询ry数据库表信息
-     * 
-     * @param tableInfo 表信息
-     * @return 数据库表列表
-     */
-    public List<TableInfo> selectTableList(TableInfo tableInfo);
-
-    /**
-     * 根据表名称查询信息
-     * 
-     * @param tableName 表名称
-     * @return 表信息
-     */
-    public TableInfo selectTableByName(String tableName);
-
-    /**
-     * 根据表名称查询列信息
-     * 
-     * @param tableName 表名称
-     * @return 列信息
-     */
-    public List<ColumnInfo> selectTableColumnsByName(String tableName);
-}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/mapper/GenTableColumnMapper.java b/src/main/java/com/huaheng/pc/tool/gen/mapper/GenTableColumnMapper.java
new file mode 100644
index 0000000..08f0a36
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/mapper/GenTableColumnMapper.java
@@ -0,0 +1,52 @@
+package com.huaheng.pc.tool.gen.mapper;
+
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+
+import java.util.List;
+
+/**
+ * 业务字段 数据层
+ *
+ * @author ruoyi
+ */
+public interface GenTableColumnMapper {
+    /**
+     * 根据表名称查询列信息
+     *
+     * @param tableName 表名称
+     * @return 列信息
+     */
+    List<GenTableColumn> selectDbTableColumnsByName(String tableName);
+
+    /**
+     * 查询业务字段列表
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 业务字段集合
+     */
+    List<GenTableColumn> selectGenTableColumnListByTableId(GenTableColumn genTableColumn);
+
+    /**
+     * 新增业务字段
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    int insertGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 修改业务字段
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    int updateGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 批量删除业务字段
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    int deleteGenTableColumnByIds(Long[] ids);
+}
\ No newline at end of file
diff --git a/src/main/java/com/huaheng/pc/tool/gen/mapper/GenTableMapper.java b/src/main/java/com/huaheng/pc/tool/gen/mapper/GenTableMapper.java
new file mode 100644
index 0000000..ea02fe4
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/mapper/GenTableMapper.java
@@ -0,0 +1,81 @@
+package com.huaheng.pc.tool.gen.mapper;
+
+import com.huaheng.pc.tool.gen.domain.GenTable;
+
+import java.util.List;
+
+/**
+ * 业务 数据层
+ */
+public interface GenTableMapper {
+    /**
+     * 查询业务列表
+     *
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    List<GenTable> selectGenTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     *
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    List<GenTable> selectDbTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     *
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    List<GenTable> selectDbTableListByNames(String[] tableNames);
+
+    /**
+     * 查询所有表信息
+     *
+     * @return 表信息集合
+     */
+    List<GenTable> selectGenTableAll();
+
+    /**
+     * 查询表ID业务信息
+     *
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    GenTable selectGenTableById(Long id);
+
+    /**
+     * 查询表名称业务信息
+     *
+     * @param tableName 表名称
+     * @return 业务信息
+     */
+    GenTable selectGenTableByName(String tableName);
+
+    /**
+     * 新增业务
+     *
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    int insertGenTable(GenTable genTable);
+
+    /**
+     * 修改业务
+     *
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    int updateGenTable(GenTable genTable);
+
+    /**
+     * 批量删除业务
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    int deleteGenTableByIds(Long[] ids);
+}
\ No newline at end of file
diff --git a/src/main/java/com/huaheng/pc/tool/gen/service/GenServiceImpl.java b/src/main/java/com/huaheng/pc/tool/gen/service/GenServiceImpl.java
deleted file mode 100644
index 0a00755..0000000
--- a/src/main/java/com/huaheng/pc/tool/gen/service/GenServiceImpl.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.huaheng.pc.tool.gen.service;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import org.apache.commons.io.IOUtils;
-import org.apache.velocity.Template;
-import org.apache.velocity.VelocityContext;
-import org.apache.velocity.app.Velocity;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import com.huaheng.common.constant.Constants;
-import com.huaheng.common.exception.base.BaseException;
-import com.huaheng.common.utils.StringUtils;
-import com.huaheng.framework.config.GenConfig;
-import com.huaheng.pc.tool.gen.domain.ColumnInfo;
-import com.huaheng.pc.tool.gen.domain.TableInfo;
-import com.huaheng.pc.tool.gen.mapper.GenMapper;
-import com.huaheng.pc.tool.gen.util.GenUtils;
-import com.huaheng.pc.tool.gen.util.VelocityInitializer;
-
-/**
- * 代码生成 服务层处理
- * 
- * @author huaheng
- */
-@Service
-public class GenServiceImpl implements IGenService
-{
-    @Autowired
-    private GenMapper genMapper;
-
-    /**
-     * 查询ry数据库表信息
-     * 
-     * @param tableInfo 表信息
-     * @return 数据库表列表
-     */
-    @Override
-    public List<TableInfo> selectTableList(TableInfo tableInfo)
-    {
-        return genMapper.selectTableList(tableInfo);
-    }
-
-    /**
-     * 生成代码
-     * 
-     * @param tableName 表名称
-     * @return 数据
-     */
-    @Override
-    public byte[] generatorCode(String tableName)
-    {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        ZipOutputStream zip = new ZipOutputStream(outputStream);
-        // 查询表信息
-        TableInfo table = genMapper.selectTableByName(tableName);
-        // 查询列信息
-        List<ColumnInfo> columns = genMapper.selectTableColumnsByName(tableName);
-        // 生成代码
-        generatorCode(table, columns, zip);
-        IOUtils.closeQuietly(zip);
-        return outputStream.toByteArray();
-    }
-
-    /**
-     * 批量生成代码
-     * 
-     * @param tableNames 表数组
-     * @return 数据
-     */
-    @Override
-    public byte[] generatorCode(String[] tableNames)
-    {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        ZipOutputStream zip = new ZipOutputStream(outputStream);
-        for (String tableName : tableNames)
-        {
-            // 查询表信息
-            TableInfo table = genMapper.selectTableByName(tableName);
-            // 查询列信息
-            List<ColumnInfo> columns = genMapper.selectTableColumnsByName(tableName);
-            // 生成代码
-            generatorCode(table, columns, zip);
-        }
-        IOUtils.closeQuietly(zip);
-        return outputStream.toByteArray();
-    }
-
-    /**
-     * 生成代码
-     */
-    public void generatorCode(TableInfo table, List<ColumnInfo> columns, ZipOutputStream zip)
-    {
-        // 表名转换成Java属性名
-        String className = GenUtils.tableToJava(table.getTableName());
-        table.setClassName(className);
-        table.setClassname(StringUtils.uncapitalize(className));
-        // 列信息
-        table.setColumns(GenUtils.transColums(columns));
-        // 设置主键
-        table.setPrimaryKey(table.getColumnsLast());
-
-        VelocityInitializer.initVelocity();
-
-        String packageName = GenConfig.getPackageName();
-        String moduleName = GenUtils.getModuleName(packageName);
-
-        VelocityContext context = GenUtils.getVelocityContext(table);
-
-        // 获取模板列表
-        List<String> templates = GenUtils.getTemplates();
-        for (String template : templates)
-        {
-            // 渲染模板
-            StringWriter sw = new StringWriter();
-            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
-            tpl.merge(context, sw);
-            try
-            {
-                // 添加到zip
-                zip.putNextEntry(new ZipEntry(GenUtils.getFileName(template, table, moduleName)));
-                IOUtils.write(sw.toString(), zip, Constants.UTF8);
-                IOUtils.closeQuietly(sw);
-                zip.closeEntry();
-            }
-            catch (IOException e)
-            {
-                throw new BaseException("渲染模板失败,表名:" + table.getTableName(), e.getMessage());
-            }
-        }
-    }
-}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/service/IGenService.java b/src/main/java/com/huaheng/pc/tool/gen/service/IGenService.java
deleted file mode 100644
index d73c635..0000000
--- a/src/main/java/com/huaheng/pc/tool/gen/service/IGenService.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.huaheng.pc.tool.gen.service;
-
-import java.util.List;
-import com.huaheng.pc.tool.gen.domain.TableInfo;
-
-/**
- * 代码生成 服务层
- * 
- * @author huaheng
- */
-public interface IGenService
-{
-    /**
-     * 查询ry数据库表信息
-     * 
-     * @param tableInfo 表信息
-     * @return 数据库表列表
-     */
-    public List<TableInfo> selectTableList(TableInfo tableInfo);
-
-    /**
-     * 生成代码
-     * 
-     * @param tableName 表名称
-     * @return 数据
-     */
-    public byte[] generatorCode(String tableName);
-    
-    /**
-     * 批量生成代码
-     * 
-     * @param tableNames 表数组
-     * @return 数据
-     */
-    public byte[] generatorCode(String[] tableNames);
-
-}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/service/IGenTableColumnService.java b/src/main/java/com/huaheng/pc/tool/gen/service/IGenTableColumnService.java
new file mode 100644
index 0000000..1e26eee
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/service/IGenTableColumnService.java
@@ -0,0 +1,45 @@
+package com.huaheng.pc.tool.gen.service;
+
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+
+import java.util.List;
+
+/**
+ * 业务字段 服务层
+ * @author Enzo Cotter
+ * @date 2020/6/30
+ */
+public interface IGenTableColumnService {
+
+    /**
+     * 查询业务字段列表
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 业务字段集合
+     */
+    List<GenTableColumn> selectGenTableColumnListByTableId(GenTableColumn genTableColumn);
+
+    /**
+     * 新增业务字段
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    int insertGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 修改业务字段
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    int updateGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 删除业务字段信息
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    int deleteGenTableColumnByIds(String ids);
+}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/service/IGenTableService.java b/src/main/java/com/huaheng/pc/tool/gen/service/IGenTableService.java
new file mode 100644
index 0000000..98f1e1a
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/service/IGenTableService.java
@@ -0,0 +1,108 @@
+package com.huaheng.pc.tool.gen.service;
+
+import com.huaheng.pc.tool.gen.domain.GenTable;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 业务 服务层
+ * @author Enzo Cotter
+ * @date 2020/6/30
+ */
+public interface IGenTableService {
+
+    /**
+     * 查询业务列表
+     *
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    public List<GenTable> selectGenTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     *
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     *
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableListByNames(String[] tableNames);
+
+    /**
+     * 查询所有表信息
+     *
+     * @return 表信息集合
+     */
+    public List<GenTable> selectGenTableAll();
+
+    /**
+     * 查询业务信息
+     *
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    public GenTable selectGenTableById(Long id);
+
+    /**
+     * 修改业务
+     *
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    public void updateGenTable(GenTable genTable);
+
+    /**
+     * 删除业务信息
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public void deleteGenTableByIds(String ids);
+
+    /**
+     * 导入表结构
+     *
+     * @param tableList 导入表列表
+     * @param operName 操作人员
+     */
+    public void importGenTable(List<GenTable> tableList, String operName);
+
+    /**
+     * 预览代码
+     *
+     * @param tableId 表编号
+     * @return 预览数据列表
+     */
+    public Map<String, String> previewCode(Long tableId);
+
+    /**
+     * 生成代码
+     *
+     * @param tableName 表名称
+     * @return 数据
+     */
+    public byte[] generatorCode(String tableName);
+
+    /**
+     * 批量生成代码
+     *
+     * @param tableNames 表数组
+     * @return 数据
+     */
+    public byte[] generatorCode(String[] tableNames);
+
+    /**
+     * 修改保存参数校验
+     *
+     * @param genTable 业务信息
+     */
+    public void validateEdit(GenTable genTable);
+}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/service/impl/GenTableColumnServiceImpl.java b/src/main/java/com/huaheng/pc/tool/gen/service/impl/GenTableColumnServiceImpl.java
new file mode 100644
index 0000000..28eb002
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/service/impl/GenTableColumnServiceImpl.java
@@ -0,0 +1,66 @@
+package com.huaheng.pc.tool.gen.service.impl;
+
+import com.huaheng.common.support.Convert;
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+import com.huaheng.pc.tool.gen.mapper.GenTableColumnMapper;
+import com.huaheng.pc.tool.gen.service.IGenTableColumnService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 业务字段 服务层实现
+ * @author Enzo Cotter
+ * @date 2020/6/30
+ */
+@Service
+public class GenTableColumnServiceImpl implements IGenTableColumnService {
+
+    @Autowired
+    private GenTableColumnMapper genTableColumnMapper;
+
+    /**
+     * 查询业务字段列表
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 业务字段集合
+     */
+    @Override
+    public List<GenTableColumn> selectGenTableColumnListByTableId(GenTableColumn genTableColumn) {
+        return genTableColumnMapper.selectGenTableColumnListByTableId(genTableColumn);
+    }
+
+    /**
+     * 新增业务字段
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    @Override
+    public int insertGenTableColumn(GenTableColumn genTableColumn) {
+        return genTableColumnMapper.insertGenTableColumn(genTableColumn);
+    }
+
+    /**
+     * 修改业务字段
+     *
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    @Override
+    public int updateGenTableColumn(GenTableColumn genTableColumn) {
+        return genTableColumnMapper.updateGenTableColumn(genTableColumn);
+    }
+
+    /**
+     * 删除业务字段对象
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    public int deleteGenTableColumnByIds(String ids) {
+        return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids));
+    }
+}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/service/impl/GenTableServiceImpl.java b/src/main/java/com/huaheng/pc/tool/gen/service/impl/GenTableServiceImpl.java
new file mode 100644
index 0000000..74e2dbf
--- /dev/null
+++ b/src/main/java/com/huaheng/pc/tool/gen/service/impl/GenTableServiceImpl.java
@@ -0,0 +1,348 @@
+package com.huaheng.pc.tool.gen.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.huaheng.common.constant.Constants;
+import com.huaheng.common.constant.GenConstants;
+import com.huaheng.common.exception.BusinessException;
+import com.huaheng.common.support.Convert;
+import com.huaheng.common.utils.StringUtils;
+import com.huaheng.common.utils.VelocityUtils;
+import com.huaheng.pc.tool.gen.domain.GenTable;
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+import com.huaheng.pc.tool.gen.mapper.GenTableColumnMapper;
+import com.huaheng.pc.tool.gen.mapper.GenTableMapper;
+import com.huaheng.pc.tool.gen.service.IGenTableService;
+import com.huaheng.pc.tool.gen.util.GenUtils;
+import com.huaheng.pc.tool.gen.util.VelocityInitializer;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.ByteArrayOutputStream;
+import org.apache.commons.io.IOUtils;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Created by Enzo Cotter on 2020/6/30.
+ */
+@Service
+public class GenTableServiceImpl implements IGenTableService {
+
+    private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);
+
+    @Resource
+    private GenTableMapper genTableMapper;
+
+    @Resource
+    private GenTableColumnMapper genTableColumnMapper;
+
+    /**
+     * 查询业务信息
+     *
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    @Override
+    public GenTable selectGenTableById(Long id) {
+        GenTable genTable = genTableMapper.selectGenTableById(id);
+        setTableFromOptions(genTable);
+        return genTable;
+    }
+
+    /**
+     * 查询业务列表
+     *
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    @Override
+    public List<GenTable> selectGenTableList(GenTable genTable) {
+        return genTableMapper.selectGenTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     *
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableList(GenTable genTable) {
+        return genTableMapper.selectDbTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     *
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableListByNames(String[] tableNames) {
+        return genTableMapper.selectDbTableListByNames(tableNames);
+    }
+
+    /**
+     * 查询所有表信息
+     *
+     * @return 表信息集合
+     */
+    @Override
+    public List<GenTable> selectGenTableAll() {
+        return genTableMapper.selectGenTableAll();
+    }
+
+    /**
+     * 修改业务
+     *
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void updateGenTable(GenTable genTable) {
+        String options = JSON.toJSONString(genTable.getParams());
+        genTable.setOptions(options);
+        int row = genTableMapper.updateGenTable(genTable);
+        if (row > 0) {
+            for (GenTableColumn cenTableColumn : genTable.getColumns()) {
+                genTableColumnMapper.updateGenTableColumn(cenTableColumn);
+            }
+        }
+    }
+
+    /**
+     * 删除业务对象
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void deleteGenTableByIds(String ids) {
+        genTableMapper.deleteGenTableByIds(Convert.toLongArray(ids));
+        genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids));
+    }
+
+    /**
+     * 导入表结构
+     *
+     * @param tableList 导入表列表
+     * @param operName  操作人员
+     */
+    @Override
+    @Transactional
+    public void importGenTable(List<GenTable> tableList, String operName) {
+        try {
+            for (GenTable table : tableList) {
+                String tableName = table.getTableName();
+                GenUtils.initTable(table, operName);
+                int row = genTableMapper.insertGenTable(table);
+                if (row > 0) {
+                    // 保存列信息
+                    List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
+                    for (GenTableColumn column : genTableColumns) {
+                        GenUtils.initColumnField(column, table);
+                        genTableColumnMapper.insertGenTableColumn(column);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new BusinessException("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 预览代码
+     *
+     * @param tableId 表编号
+     * @return 预览数据列表
+     */
+    @Override
+    public Map<String, String> previewCode(Long tableId) {
+        Map<String, String> dataMap = new LinkedHashMap<>();
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableById(tableId);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+        for (String template : templates) {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            dataMap.put(template, sw.toString());
+        }
+        return dataMap;
+    }
+
+    /**
+     * 生成代码
+     *
+     * @param tableName 表名称
+     * @return 数据
+     */
+    @Override
+    public byte[] generatorCode(String tableName) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        generatorCode(tableName, zip);
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 批量生成代码
+     *
+     * @param tableNames 表数组
+     * @return 数据
+     */
+    @Override
+    public byte[] generatorCode(String[] tableNames) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        for (String tableName : tableNames) {
+            generatorCode(tableName, zip);
+        }
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 查询表信息并生成代码
+     */
+    private void generatorCode(String tableName, ZipOutputStream zip) {
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+        for (String template : templates) {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            try {
+                // 添加到zip
+                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
+                IOUtils.write(sw.toString(), zip, Constants.UTF8);
+                IOUtils.closeQuietly(sw);
+                zip.flush();
+                zip.closeEntry();
+            } catch (IOException e) {
+                log.error("渲染模板失败,表名:" + table.getTableName(), e);
+            }
+        }
+    }
+
+    /**
+     * 修改保存参数校验
+     *
+     * @param genTable 业务信息
+     */
+    @Override
+    public void validateEdit(GenTable genTable) {
+        if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
+            String options = JSON.toJSONString(genTable.getParams());
+            JSONObject paramsObj = JSONObject.parseObject(options);
+            if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) {
+                throw new BusinessException("树编码字段不能为空");
+            } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) {
+                throw new BusinessException("树父编码字段不能为空");
+            } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) {
+                throw new BusinessException("树名称字段不能为空");
+            }
+        } else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) {
+            if (StringUtils.isEmpty(genTable.getSubTableName())) {
+                throw new BusinessException("关联子表的表名不能为空");
+            } else if (StringUtils.isEmpty(genTable.getSubTableFkName())) {
+                throw new BusinessException("子表关联的外键名不能为空");
+            }
+        }
+    }
+
+    /**
+     * 设置主键列信息
+     *
+     * @param table 业务表信息
+     */
+    public void setPkColumn(GenTable table) {
+
+        for (GenTableColumn column : table.getColumns()) {
+            if (column.isPk()) {
+                table.setPkColumn(column);
+                break;
+            }
+        }
+        if (StringUtils.isNull(table.getPkColumn())) {
+            table.setPkColumn(table.getColumns().get(0));
+        }
+        if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
+            for (GenTableColumn column : table.getSubTable().getColumns()) {
+                if (column.isPk()) {
+                    table.getSubTable().setPkColumn(column);
+                    break;
+                }
+            }
+            if (StringUtils.isNull(table.getSubTable().getPkColumn())) {
+                table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
+            }
+        }
+    }
+
+    /**
+     * 设置主子表信息
+     *
+     * @param table 业务表信息
+     */
+    public void setSubTable(GenTable table) {
+        String subTableName = table.getSubTableName();
+        if (StringUtils.isNotEmpty(subTableName)) {
+            table.setSubTable(genTableMapper.selectGenTableByName(subTableName));
+        }
+    }
+
+    /**
+     * 设置代码生成其他选项值
+     *
+     * @param genTable 设置后的生成对象
+     */
+    public void setTableFromOptions(GenTable genTable) {
+        JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions());
+        if (StringUtils.isNotNull(paramsObj)) {
+            String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
+            String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
+            String treeName = paramsObj.getString(GenConstants.TREE_NAME);
+            genTable.setTreeCode(treeCode);
+            genTable.setTreeParentCode(treeParentCode);
+            genTable.setTreeName(treeName);
+        }
+    }
+}
diff --git a/src/main/java/com/huaheng/pc/tool/gen/util/GenUtils.java b/src/main/java/com/huaheng/pc/tool/gen/util/GenUtils.java
index 97cfba0..9b451c3 100644
--- a/src/main/java/com/huaheng/pc/tool/gen/util/GenUtils.java
+++ b/src/main/java/com/huaheng/pc/tool/gen/util/GenUtils.java
@@ -1,206 +1,234 @@
 package com.huaheng.pc.tool.gen.util;
 
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.velocity.VelocityContext;
-import com.huaheng.common.constant.Constants;
-import com.huaheng.common.constant.CommonMap;
-import com.huaheng.common.utils.DateUtils;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+
+import com.huaheng.common.constant.GenConstants;
 import com.huaheng.common.utils.StringUtils;
 import com.huaheng.framework.config.GenConfig;
-import com.huaheng.pc.tool.gen.domain.ColumnInfo;
-import com.huaheng.pc.tool.gen.domain.TableInfo;
+import com.huaheng.pc.tool.gen.domain.GenTable;
+import com.huaheng.pc.tool.gen.domain.GenTableColumn;
+import org.apache.commons.lang3.RegExUtils;
+
 
 /**
  * 代码生成器 工具类
- * 
- * @author huaheng
+ *
+ * @author ruoyi
  */
-public class GenUtils
-{
-    /** 项目空间路径 */
-    private static final String PROJECT_PATH = "main/java/com/huaheng/pc";
+public class GenUtils {
+    /**
+     * 初始化表信息
+     */
+    public static void initTable(GenTable genTable, String operName) {
+        genTable.setClassName(convertClassName(genTable.getTableName()));
+        genTable.setPackageName(GenConfig.getPackageName());
+        genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
+        genTable.setBusinessName(getBusinessName(genTable.getTableName()));
+        genTable.setFunctionName(replaceText(genTable.getTableComment()));
+        genTable.setFunctionAuthor(GenConfig.getAuthor());
+        genTable.setCreateBy(operName);
+    }
 
-    /** mybatis空间路径 */
-    private static final String MYBATIS_PATH = "main/resources/mybatis";
+    /**
+     * 初始化列属性字段
+     */
+    public static void initColumnField(GenTableColumn column, GenTable table) {
+        String dataType = getDbType(column.getColumnType());
+        String columnName = column.getColumnName();
+        column.setTableId(table.getTableId());
+        column.setCreateBy(table.getCreateBy());
+        // 设置java字段名
+        column.setJavaField(StringUtils.toCamelCase(columnName));
+
+        if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType)) {
+            column.setJavaType(GenConstants.TYPE_STRING);
+            // 字符串长度超过500设置为文本域
+            Integer columnLength = getColumnLength(column.getColumnType());
+            String htmlType = columnLength >= 500 ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
+            column.setHtmlType(htmlType);
+        } else if (arraysContains(GenConstants.COLUMNTYPE_DATETIME, dataType)) {
+            column.setJavaType(GenConstants.TYPE_DATETIME);
+            column.setHtmlType(GenConstants.HTML_DATETIME);
+        }else if (arraysContains(GenConstants.COLUMNTYPE_DATE, dataType)) {
+            column.setJavaType(GenConstants.TYPE_DATE);
+            column.setHtmlType(GenConstants.HTML_DATETIME);
+        }else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
+            column.setJavaType(GenConstants.TYPE_TIME);
+            column.setHtmlType(GenConstants.HTML_DATETIME);
+        }else if (arraysContains(GenConstants.COLUMNTYPE_BIT, dataType)) {
+            column.setJavaType(GenConstants.TYPE_INTEGER);
+            column.setHtmlType(GenConstants.HTML_SELECT);
+        } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
+            column.setHtmlType(GenConstants.HTML_INPUT);
+
+            // 如果是浮点型
+            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
+            if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
+                column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
+            }
+            // 如果是整形
+            else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) {
+                column.setJavaType(GenConstants.TYPE_INTEGER);
+            }
+            // 长整形
+            else {
+                column.setJavaType(GenConstants.TYPE_LONG);
+            }
+        }
 
-    /** html空间路径 */
-    private static final String TEMPLATES_PATH = "main/resources/templates";
+        // 插入字段(默认所有字段都需要插入)
+        column.setIsInsert(GenConstants.REQUIRE);
+
+        // 编辑字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) {
+            column.setIsEdit(GenConstants.REQUIRE);
+        }
+        // 列表字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) {
+            column.setIsList(GenConstants.REQUIRE);
+        }
+        // 查询字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) {
+            column.setIsQuery(GenConstants.REQUIRE);
+        }
+
+        // 查询字段类型
+        if (StringUtils.endsWithIgnoreCase(columnName, "name")) {
+            column.setQueryType(GenConstants.QUERY_LIKE);
+        }
+        // 状态字段设置单选框
+        if (StringUtils.endsWithIgnoreCase(columnName, "status")) {
+            column.setHtmlType(GenConstants.HTML_RADIO);
+        }
+        // 类型&性别字段设置下拉框
+        else if (StringUtils.endsWithIgnoreCase(columnName, "type")
+                || StringUtils.endsWithIgnoreCase(columnName, "sex")) {
+            column.setHtmlType(GenConstants.HTML_SELECT);
+        }
+    }
 
     /**
-     * 设置列信息
+     * 校验数组是否包含指定值
+     *
+     * @param arr         数组
+     * @param targetValue 值
+     * @return 是否包含
      */
-    public static List<ColumnInfo> transColums(List<ColumnInfo> columns)
-    {
-        // 列信息
-        List<ColumnInfo> columsList = new ArrayList<>();
-        for (ColumnInfo column : columns)
-        {
-            // 列名转换成Java属性名
-            String attrName = StringUtils.convertToCamelCase(column.getColumnName());
-            column.setAttrName(attrName);
-            column.setAttrname(StringUtils.uncapitalize(attrName));
-
-            // 列的数据类型,转换成Java类型
-            String attrType = CommonMap.javaTypeMap.get(column.getDataType());
-            column.setAttrType(attrType);
-
-            columsList.add(column);
-        }
-        return columsList;
+    public static boolean arraysContains(String[] arr, String targetValue) {
+        return Arrays.asList(arr).contains(targetValue);
     }
 
     /**
-     * 获取模板信息
-     * 
-     * @return 模板列表
+     * 获取模块名
+     *
+     * @param packageName 包名
+     * @return 模块名
      */
-    public static VelocityContext getVelocityContext(TableInfo table)
-    {
-        // java对象数据传递到模板文件vm
-        VelocityContext velocityContext = new VelocityContext();
-        String packageName = GenConfig.getPackageName();
-        velocityContext.put("tableName", table.getTableName());
-        velocityContext.put("tableComment", replaceKeyword(table.getTableComment()));
-        velocityContext.put("primaryKey", table.getPrimaryKey());
-        velocityContext.put("className", table.getClassName());
-        velocityContext.put("classname", table.getClassname());
-        velocityContext.put("moduleName", GenUtils.getModuleName(packageName));
-        velocityContext.put("columns", table.getColumns());
-        velocityContext.put("package", packageName + "." + table.getClassname());
-        velocityContext.put("author", GenConfig.getAuthor());
-        velocityContext.put("datetime", DateUtils.getDate());
-        return velocityContext;
+    public static String getModuleName(String packageName) {
+        int lastIndex = packageName.lastIndexOf(".");
+        int nameLength = packageName.length();
+        String moduleName = StringUtils.substring(packageName, lastIndex + 1, nameLength);
+        return moduleName;
     }
 
     /**
-     * 获取模板信息
-     * 
-     * @return 模板列表
+     * 获取业务名
+     *
+     * @param tableName 表名
+     * @return 业务名
      */
-    public static List<String> getTemplates()
-    {
-        List<String> templates = new ArrayList<String>();
-        templates.add("templates/vm/java/domain.java.vm");
-        templates.add("templates/vm/java/Mapper.java.vm");
-        templates.add("templates/vm/java/Service.java.vm");
-        templates.add("templates/vm/java/ServiceImpl.java.vm");
-        templates.add("templates/vm/java/Controller.java.vm");
-        templates.add("templates/vm/xml/Mapper.xml.vm");
-        templates.add("templates/vm/html/list.html.vm");
-        templates.add("templates/vm/html/add.html.vm");
-        templates.add("templates/vm/html/edit.html.vm");
-        templates.add("templates/vm/sql/sql.vm");
-        return templates;
+    public static String getBusinessName(String tableName) {
+        int lastIndex = tableName.lastIndexOf("_");
+        int nameLength = tableName.length();
+        String businessName = StringUtils.substring(tableName, lastIndex + 1, nameLength);
+        return businessName;
     }
 
     /**
      * 表名转换成Java类名
+     *
+     * @param tableName 表名称
+     * @return 类名
      */
-    public static String tableToJava(String tableName)
-    {
-        if (Constants.AUTO_REOMVE_PRE.equals(GenConfig.getAutoRemovePre()))
-        {
-            tableName = tableName.substring(tableName.indexOf("_") + 1);
-        }
-        if (StringUtils.isNotEmpty(GenConfig.getTablePrefix()))
-        {
-            tableName = tableName.replace(GenConfig.getTablePrefix(), "");
+    public static String convertClassName(String tableName) {
+        boolean autoRemovePre = GenConfig.getAutoRemovePre();
+        String tablePrefix = GenConfig.getTablePrefix();
+        if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
+            String[] searchList = StringUtils.split(tablePrefix, ",");
+            tableName = replaceFirst(tableName, searchList);
         }
         return StringUtils.convertToCamelCase(tableName);
     }
 
     /**
-     * 获取文件名
+     * 批量替换前缀
+     *
+     * @param replacementm 替换值
+     * @param searchList   替换列表
+     * @return
      */
-    public static String getFileName(String template, TableInfo table, String moduleName)
-    {
-        // 小写类名
-        String classname = table.getClassname();
-        // 大写类名
-        String className = table.getClassName();
-        String javaPath = PROJECT_PATH + "/" + moduleName + "/";
-        String mybatisPath = MYBATIS_PATH + "/" + moduleName + "/" + className;
-        String htmlPath = TEMPLATES_PATH + "/" + moduleName + "/" + classname;
-
-        if (StringUtils.isNotEmpty(classname))
-        {
-            javaPath += classname.replace(".", "/") + "/";
-        }
-
-        if (template.contains("domain.java.vm"))
-        {
-            return javaPath + "domain" + "/" + className + ".java";
-        }
-
-        if (template.contains("mapper.java.vm"))
-        {
-            return javaPath + "mapper" + "/" + className + "mapper.java";
-        }
-
-        if (template.contains("Service.java.vm"))
-        {
-            return javaPath + "service" + "/" + "I" + className + "Service.java";
-        }
-
-        if (template.contains("ServiceImpl.java.vm"))
-        {
-            return javaPath + "service" + "/" + className + "ServiceImpl.java";
-        }
-
-        if (template.contains("Controller.java.vm"))
-        {
-            return javaPath + "controller" + "/" + className + "Controller.java";
-        }
-
-        if (template.contains("mapper.xml.vm"))
-        {
-            return mybatisPath + "mapper.xml";
+    public static String replaceFirst(String replacementm, String[] searchList) {
+        String text = replacementm;
+        for (String searchString : searchList) {
+            if (replacementm.startsWith(searchString)) {
+                text = replacementm.replaceFirst(searchString, "");
+                break;
+            }
         }
+        return text;
+    }
 
-        if (template.contains("list.html.vm"))
-        {
-            return htmlPath + "/" + classname + ".html";
-        }
-        if (template.contains("add.html.vm"))
-        {
-            return htmlPath + "/" + "add.html";
-        }
-        if (template.contains("edit.html.vm"))
-        {
-            return htmlPath + "/" + "edit.html";
-        }
-        if (template.contains("sql.vm"))
-        {
-            return classname + "Menu.sql";
-        }
-        return null;
+    /**
+     * 关键字替换
+     *
+     * @param text 需要被替换的名字
+     * @return 替换后的名字
+     */
+    public static String replaceText(String text) {
+        return RegExUtils.replaceAll(text, "(?:表|若依)", "");
     }
 
     /**
-     * 获取模块名
-     * 
-     * @param packageName 包名
-     * @return 模块名
+     * 获取数据库类型字段
+     *
+     * @param columnType 列类型
+     * @return 截取后的列类型
      */
-    public static String getModuleName(String packageName)
-    {
-        int lastIndex = packageName.lastIndexOf(".");
-        int nameLength = packageName.length();
-        String moduleName = StringUtils.substring(packageName, lastIndex + 1, nameLength);
-        return moduleName;
+    public static String getDbType(String columnType) {
+        if (StringUtils.indexOf(columnType, "(") > 0) {
+            return StringUtils.substringBefore(columnType, "(");
+        } else {
+            return columnType;
+        }
     }
 
-    public static String replaceKeyword(String keyword)
-    {
-        String keyName = keyword.replaceAll("(?:表|信息)", "");
-        return keyName;
+    /**
+     * 获取字段长度
+     *
+     * @param columnType 列类型
+     * @return 截取后的列类型
+     */
+    public static Integer getColumnLength(String columnType) {
+        if (StringUtils.indexOf(columnType, "(") > 0) {
+            String length = StringUtils.substringBetween(columnType, "(", ")");
+            return Integer.valueOf(length);
+        } else {
+            return 0;
+        }
     }
 
-    public static void main(String[] args)
-    {
-        System.out.println(StringUtils.convertToCamelCase("userName"));
-        System.out.println(replaceKeyword("岗位信息表"));
-        System.out.println(getModuleName("com.huaheng.pc.system"));
+    /**
+     * 获取空数组列表
+     *
+     * @param length 长度
+     * @return 数组信息
+     */
+    public static String[] emptyList(int length) {
+        String[] values = new String[length];
+        for (int i = 0; i < length; i++) {
+            values[i] = StringUtils.EMPTY;
+        }
+        return values;
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/resources/generator.yml b/src/main/resources/generator.yml
new file mode 100644
index 0000000..6788a52
--- /dev/null
+++ b/src/main/resources/generator.yml
@@ -0,0 +1,10 @@
+# 代码生成
+gen:
+  # 作者
+  author: huaheng
+  # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
+  packageName: com.huaheng.pc
+  # 自动去除表前缀,默认是false
+  autoRemovePre: false
+  # 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
+  tablePrefix: sys_
\ No newline at end of file
diff --git a/src/main/resources/mybatis/system/DictTypeMapper.xml b/src/main/resources/mybatis/system/DictTypeMapper.xml
index 57f9c35..da2108b 100644
--- a/src/main/resources/mybatis/system/DictTypeMapper.xml
+++ b/src/main/resources/mybatis/system/DictTypeMapper.xml
@@ -45,7 +45,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 			</if>
 	    </where>
 	</select>
-	
+
+	<select id="selectDictTypeByType" parameterType="String" resultMap="DictTypeResult">
+		<include refid="selectDictTypeVo"/>
+		where dictType = #{dictType}
+	</select>
+
 	<select id="selectDictTypeAll" resultMap="DictTypeResult">
 		<include refid="selectDictTypeVo"/>
 		where warehouseCode=#{warehouseCode}
diff --git a/src/main/resources/mybatis/tool/GenTableColumnMapper.xml b/src/main/resources/mybatis/tool/GenTableColumnMapper.xml
new file mode 100644
index 0000000..f88c809
--- /dev/null
+++ b/src/main/resources/mybatis/tool/GenTableColumnMapper.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.huaheng.pc.tool.gen.mapper.GenTableColumnMapper">
+
+    <resultMap type="com.huaheng.pc.tool.gen.domain.GenTableColumn" id="GenTableColumnResult">
+        <id     property="columnId"       column="column_id"      />
+        <result property="tableId"        column="table_id"       />
+        <result property="columnName"     column="column_name"    />
+        <result property="columnComment"  column="column_comment" />
+        <result property="columnType"     column="column_type"    />
+        <result property="javaType"       column="java_type"      />
+        <result property="javaField"      column="java_field"     />
+        <result property="isPk"           column="is_pk"          />
+        <result property="isIncrement"    column="is_increment"   />
+        <result property="isRequired"     column="is_required"    />
+        <result property="isInsert"       column="is_insert"      />
+        <result property="isEdit"         column="is_edit"        />
+        <result property="isList"         column="is_list"        />
+        <result property="isQuery"        column="is_query"       />
+        <result property="queryType"      column="query_type"     />
+        <result property="htmlType"       column="html_type"      />
+        <result property="dictType"       column="dict_type"      />
+        <result property="sort"           column="sort"           />
+        <result property="createBy"       column="create_by"      />
+        <result property="createTime"     column="create_time"    />
+        <result property="updateBy"       column="update_by"      />
+        <result property="updateTime"     column="update_time"    />
+    </resultMap>
+
+	<sql id="selectGenTableColumnVo">
+        select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column
+    </sql>
+
+    <select id="selectGenTableColumnListByTableId" parameterType="com.huaheng.pc.tool.gen.domain.GenTableColumn" resultMap="GenTableColumnResult">
+        <include refid="selectGenTableColumnVo"/>
+        where table_id = #{tableId}
+        order by sort
+    </select>
+
+    <select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
+		select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type
+		from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
+		order by ordinal_position
+	</select>
+
+    <insert id="insertGenTableColumn" parameterType="com.huaheng.pc.tool.gen.domain.GenTableColumn" useGeneratedKeys="true" keyProperty="columnId">
+        insert into gen_table_column (
+			<if test="tableId != null and tableId != ''">table_id,</if>
+			<if test="columnName != null and columnName != ''">column_name,</if>
+			<if test="columnComment != null and columnComment != ''">column_comment,</if>
+			<if test="columnType != null and columnType != ''">column_type,</if>
+			<if test="javaType != null and javaType != ''">java_type,</if>
+			<if test="javaField != null  and javaField != ''">java_field,</if>
+			<if test="isPk != null and isPk != ''">is_pk,</if>
+			<if test="isIncrement != null and isIncrement != ''">is_increment,</if>
+			<if test="isRequired != null and isRequired != ''">is_required,</if>
+			<if test="isInsert != null and isInsert != ''">is_insert,</if>
+			<if test="isEdit != null and isEdit != ''">is_edit,</if>
+			<if test="isList != null and isList != ''">is_list,</if>
+			<if test="isQuery != null and isQuery != ''">is_query,</if>
+			<if test="queryType != null and queryType != ''">query_type,</if>
+			<if test="htmlType != null and htmlType != ''">html_type,</if>
+			<if test="dictType != null and dictType != ''">dict_type,</if>
+			<if test="sort != null">sort,</if>
+			<if test="createBy != null and createBy != ''">create_by,</if>
+			create_time
+         )values(
+			<if test="tableId != null and tableId != ''">#{tableId},</if>
+			<if test="columnName != null and columnName != ''">#{columnName},</if>
+			<if test="columnComment != null and columnComment != ''">#{columnComment},</if>
+			<if test="columnType != null and columnType != ''">#{columnType},</if>
+			<if test="javaType != null and javaType != ''">#{javaType},</if>
+			<if test="javaField != null and javaField != ''">#{javaField},</if>
+			<if test="isPk != null and isPk != ''">#{isPk},</if>
+			<if test="isIncrement != null and isIncrement != ''">#{isIncrement},</if>
+			<if test="isRequired != null and isRequired != ''">#{isRequired},</if>
+			<if test="isInsert != null and isInsert != ''">#{isInsert},</if>
+			<if test="isEdit != null and isEdit != ''">#{isEdit},</if>
+			<if test="isList != null and isList != ''">#{isList},</if>
+			<if test="isQuery != null and isQuery != ''">#{isQuery},</if>
+			<if test="queryType != null and queryType != ''">#{queryType},</if>
+			<if test="htmlType != null and htmlType != ''">#{htmlType},</if>
+			<if test="dictType != null and dictType != ''">#{dictType},</if>
+			<if test="sort != null">#{sort},</if>
+			<if test="createBy != null and createBy != ''">#{createBy},</if>
+			sysdate()
+         )
+    </insert>
+
+    <update id="updateGenTableColumn" parameterType="com.huaheng.pc.tool.gen.domain.GenTableColumn">
+        update gen_table_column
+        <set>
+            column_comment = #{columnComment},
+            java_type = #{javaType},
+            java_field = #{javaField},
+            is_insert = #{isInsert},
+            is_edit = #{isEdit},
+            is_list = #{isList},
+            is_query = #{isQuery},
+            is_required = #{isRequired},
+            query_type = #{queryType},
+            html_type = #{htmlType},
+            dict_type = #{dictType},
+            sort = #{sort},
+            update_by = #{updateBy},
+            update_time = sysdate()
+        </set>
+        where column_id = #{columnId}
+    </update>
+
+    <delete id="deleteGenTableColumnByIds" parameterType="Long">
+        delete from gen_table_column where table_id in
+        <foreach collection="array" item="tableId" open="(" separator="," close=")">
+            #{tableId}
+        </foreach>
+    </delete>
+
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mybatis/tool/GenTableMapper.xml b/src/main/resources/mybatis/tool/GenTableMapper.xml
new file mode 100644
index 0000000..f86c3c1
--- /dev/null
+++ b/src/main/resources/mybatis/tool/GenTableMapper.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.huaheng.pc.tool.gen.mapper.GenTableMapper">
+
+	<resultMap type="com.huaheng.pc.tool.gen.domain.GenTable" id="GenTableResult">
+	    <id     property="tableId"        column="table_id"          />
+		<result property="tableName"      column="table_name"        />
+		<result property="tableComment"   column="table_comment"     />
+		<result property="subTableName"   column="sub_table_name"    />
+		<result property="subTableFkName" column="sub_table_fk_name" />
+		<result property="className"      column="class_name"        />
+		<result property="tplCategory"    column="tpl_category"      />
+		<result property="packageName"    column="package_name"      />
+		<result property="moduleName"     column="module_name"       />
+		<result property="businessName"   column="business_name"     />
+		<result property="functionName"   column="function_name"     />
+		<result property="functionAuthor" column="function_author"   />
+		<result property="options"        column="options"           />
+		<result property="createBy"       column="create_by"         />
+		<result property="createTime"     column="create_time"       />
+		<result property="updateBy"       column="update_by"         />
+		<result property="updateTime"     column="update_time"       />
+		<result property="remark"         column="remark"            />
+		<collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult" />
+	</resultMap>
+
+	<resultMap type="com.huaheng.pc.tool.gen.domain.GenTableColumn" id="GenTableColumnResult">
+        <id     property="columnId"       column="column_id"      />
+        <result property="tableId"        column="table_id"       />
+        <result property="columnName"     column="column_name"    />
+        <result property="columnComment"  column="column_comment" />
+        <result property="columnType"     column="column_type"    />
+        <result property="javaType"       column="java_type"      />
+        <result property="javaField"      column="java_field"     />
+        <result property="isPk"           column="is_pk"          />
+        <result property="isIncrement"    column="is_increment"   />
+        <result property="isRequired"     column="is_required"    />
+        <result property="isInsert"       column="is_insert"      />
+        <result property="isEdit"         column="is_edit"        />
+        <result property="isList"         column="is_list"        />
+        <result property="isQuery"        column="is_query"       />
+        <result property="queryType"      column="query_type"     />
+        <result property="htmlType"       column="html_type"      />
+        <result property="dictType"       column="dict_type"      />
+        <result property="sort"           column="sort"           />
+        <result property="createBy"       column="create_by"      />
+        <result property="createTime"     column="create_time"    />
+        <result property="updateBy"       column="update_by"      />
+        <result property="updateTime"     column="update_time"    />
+    </resultMap>
+
+	<sql id="selectGenTableVo">
+        select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, package_name, module_name, business_name, function_name, function_author, options, create_by, create_time, update_by, update_time, remark from gen_table
+    </sql>
+
+    <select id="selectGenTableList" parameterType="com.huaheng.pc.tool.gen.domain.GenTable" resultMap="GenTableResult">
+		<include refid="selectGenTableVo"/>
+		<where>
+			<if test="tableName != null and tableName != ''">
+				AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
+			</if>
+			<if test="tableComment != null and tableComment != ''">
+				AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
+			</if>
+		</where>
+	</select>
+
+	<select id="selectDbTableList" parameterType="com.huaheng.pc.tool.gen.domain.GenTable" resultMap="GenTableResult">
+		select table_name, table_comment, create_time, update_time from information_schema.tables
+		where table_schema = (select database())
+		AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%'
+		AND table_name NOT IN (select table_name from gen_table)
+		<if test="tableName != null and tableName != ''">
+			AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
+		</if>
+		<if test="tableComment != null and tableComment != ''">
+			AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
+		</if>
+	</select>
+
+	<select id="selectDbTableListByNames" resultMap="GenTableResult">
+		select table_name, table_comment, create_time, update_time from information_schema.tables
+		where table_name NOT LIKE 'qrtz_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database())
+		and table_name in
+	    <foreach collection="array" item="name" open="(" separator="," close=")">
+ 			#{name}
+        </foreach>
+	</select>
+
+	<select id="selectTableByName" parameterType="String" resultMap="GenTableResult">
+		select table_name, table_comment, create_time, update_time from information_schema.tables
+		where table_comment <![CDATA[ <> ]]> '' and table_schema = (select database())
+		and table_name = #{tableName}
+	</select>
+
+	<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
+	    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark,
+			   c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
+		FROM gen_table t
+			 LEFT JOIN gen_table_column c ON t.table_id = c.table_id
+		where t.table_id = #{tableId} order by c.sort
+	</select>
+
+	<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
+	    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark,
+			   c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
+		FROM gen_table t
+			 LEFT JOIN gen_table_column c ON t.table_id = c.table_id
+		where t.table_name = #{tableName} order by c.sort
+	</select>
+
+	<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
+	    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark,
+			   c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
+		FROM gen_table t
+			 LEFT JOIN gen_table_column c ON t.table_id = c.table_id
+		order by c.sort
+	</select>
+
+	<insert id="insertGenTable" parameterType="com.huaheng.pc.tool.gen.domain.GenTable" useGeneratedKeys="true" keyProperty="tableId">
+        insert into gen_table (
+			<if test="tableName != null">table_name,</if>
+			<if test="tableComment != null and tableComment != ''">table_comment,</if>
+			<if test="className != null and className != ''">class_name,</if>
+			<if test="tplCategory != null and tplCategory != ''">tpl_category,</if>
+			<if test="packageName != null and packageName != ''">package_name,</if>
+			<if test="moduleName != null and moduleName != ''">module_name,</if>
+			<if test="businessName != null and businessName != ''">business_name,</if>
+			<if test="functionName != null and functionName != ''">function_name,</if>
+			<if test="functionAuthor != null and functionAuthor != ''">function_author,</if>
+			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+			create_time
+         )values(
+			<if test="tableName != null">#{tableName},</if>
+			<if test="tableComment != null and tableComment != ''">#{tableComment},</if>
+			<if test="className != null and className != ''">#{className},</if>
+			<if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if>
+			<if test="packageName != null and packageName != ''">#{packageName},</if>
+			<if test="moduleName != null and moduleName != ''">#{moduleName},</if>
+			<if test="businessName != null and businessName != ''">#{businessName},</if>
+			<if test="functionName != null and functionName != ''">#{functionName},</if>
+			<if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if>
+			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+			sysdate()
+         )
+    </insert>
+
+    <update id="updateGenTable" parameterType="com.huaheng.pc.tool.gen.domain.GenTable">
+        update gen_table
+        <set>
+            <if test="tableName != null">table_name = #{tableName},</if>
+            <if test="tableComment != null and tableComment != ''">table_comment = #{tableComment},</if>
+            <if test="subTableName != null and subTableName != ''">sub_table_name = #{subTableName},</if>
+            <if test="subTableFkName != null and subTableFkName != ''">sub_table_fk_name = #{subTableFkName},</if>
+            <if test="className != null and className != ''">class_name = #{className},</if>
+            <if test="functionAuthor != null and functionAuthor != ''">function_author = #{functionAuthor},</if>
+            <if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if>
+            <if test="packageName != null and packageName != ''">package_name = #{packageName},</if>
+            <if test="moduleName != null and moduleName != ''">module_name = #{moduleName},</if>
+            <if test="businessName != null and businessName != ''">business_name = #{businessName},</if>
+            <if test="functionName != null and functionName != ''">function_name = #{functionName},</if>
+            <if test="options != null and options != ''">options = #{options},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            update_time = sysdate()
+        </set>
+        where table_id = #{tableId}
+    </update>
+
+    <delete id="deleteGenTableByIds" parameterType="Long">
+        delete from gen_table where table_id in
+        <foreach collection="array" item="tableId" open="(" separator="," close=")">
+            #{tableId}
+        </foreach>
+    </delete>
+
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css
new file mode 100644
index 0000000..a9ac56e
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css
@@ -0,0 +1,550 @@
+/*!
+ * bootstrap-fileinput v5.0.4
+ * http://plugins.krajee.com/file-input
+ *
+ * Krajee default styling for bootstrap-fileinput.
+ *
+ * Author: Kartik Visweswaran
+ * Copyright: 2014 - 2019, Kartik Visweswaran, Krajee.com
+ *
+ * Licensed under the BSD-3-Clause
+ * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
+ */
+.file-loading input[type=file], input[type=file].file-loading {
+    width: 0;
+    height: 0;
+}
+
+.file-no-browse {
+    position: absolute;
+    left: 50%;
+    bottom: 20%;
+    width: 1px;
+    height: 1px;
+    font-size: 0;
+    opacity: 0;
+    border: none;
+    background: none;
+    outline: none;
+    box-shadow: none;
+}
+
+.kv-hidden, .file-caption-icon, .file-zoom-dialog .modal-header:before, .file-zoom-dialog .modal-header:after, .file-input-new .file-preview, .file-input-new .close, .file-input-new .glyphicon-file, .file-input-new .fileinput-remove-button, .file-input-new .fileinput-upload-button, .file-input-new .no-browse .input-group-btn, .file-input-ajax-new .fileinput-remove-button, .file-input-ajax-new .fileinput-upload-button, .file-input-ajax-new .no-browse .input-group-btn, .hide-content .kv-file-content, .is-locked .fileinput-upload-button, .is-locked .fileinput-remove-button {
+    display: none;
+}
+
+.btn-file input[type=file], .file-caption-icon, .file-preview .fileinput-remove, .krajee-default .file-thumb-progress, .file-zoom-dialog .btn-navigate, .file-zoom-dialog .floating-buttons {
+    position: absolute;
+}
+
+.file-caption-icon .kv-caption-icon {
+    line-height: inherit;
+}
+
+.file-input, .file-loading:before, .btn-file, .file-caption, .file-preview, .krajee-default.file-preview-frame, .krajee-default .file-thumbnail-footer, .file-zoom-dialog .modal-dialog {
+    position: relative;
+}
+
+.file-error-message pre, .file-error-message ul, .krajee-default .file-actions, .krajee-default .file-other-error {
+    text-align: left;
+}
+
+.file-error-message pre, .file-error-message ul {
+    margin: 0;
+}
+
+.krajee-default .file-drag-handle, .krajee-default .file-upload-indicator {
+    float: left;
+    margin-top: 10px;
+    width: 16px;
+    height: 16px;
+}
+
+.krajee-default .file-thumb-progress .progress, .krajee-default .file-thumb-progress .progress-bar {
+    height: 20px;
+    font-family: Verdana, Helvetica, sans-serif;
+    font-size: 9px;
+}
+
+.krajee-default .file-thumb-progress .progress, .kv-upload-progress .progress {
+    background-color: #ccc;
+}
+
+.krajee-default .file-caption-info, .krajee-default .file-size-info {
+    display: block;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    width: 160px;
+    height: 15px;
+    margin: auto;
+}
+
+.file-zoom-content > .file-object.type-video, .file-zoom-content > .file-object.type-flash, .file-zoom-content > .file-object.type-image {
+    max-width: 100%;
+    max-height: 100%;
+    width: auto;
+}
+
+.file-zoom-content > .file-object.type-video, .file-zoom-content > .file-object.type-flash {
+    height: 100%;
+}
+
+.file-zoom-content > .file-object.type-pdf, .file-zoom-content > .file-object.type-html, .file-zoom-content > .file-object.type-text, .file-zoom-content > .file-object.type-default {
+    width: 100%;
+}
+
+.file-loading:before {
+    content: " Loading...";
+    display: inline-block;
+    padding-left: 20px;
+    line-height: 16px;
+    font-size: 13px;
+    font-variant: small-caps;
+    color: #999;
+    background: transparent url(loading.gif) top left no-repeat;
+}
+
+.file-object {
+    margin: 0 0 -5px 0;
+    padding: 0;
+}
+
+.btn-file {
+    overflow: hidden;
+}
+
+.btn-file input[type=file] {
+    top: 0;
+    left: 0;
+    min-width: 100%;
+    min-height: 100%;
+    text-align: right;
+    opacity: 0;
+    background: none repeat scroll 0 0 transparent;
+    cursor: inherit;
+    display: block;
+}
+
+.btn-file ::-ms-browse {
+    font-size: 10000px;
+    width: 100%;
+    height: 100%;
+}
+
+.file-caption .file-caption-name {
+    width: 100%;
+    margin: 0;
+    padding: 0;
+    box-shadow: none;
+    border: none;
+    background: none;
+    outline: none;
+}
+
+.file-caption.icon-visible .file-caption-icon {
+    display: inline-block;
+}
+
+.file-caption.icon-visible .file-caption-name {
+    padding-left: 15px;
+}
+
+.file-caption-icon {
+    left: 8px;
+}
+
+.file-error-message {
+    color: #a94442;
+    background-color: #f2dede;
+    margin: 5px;
+    border: 1px solid #ebccd1;
+    border-radius: 4px;
+    padding: 15px;
+}
+
+.file-error-message pre {
+    margin: 5px 0;
+}
+
+.file-caption-disabled {
+    background-color: #eee;
+    cursor: not-allowed;
+    opacity: 1;
+}
+
+.file-preview {
+    border-radius: 5px;
+    border: 1px solid #ddd;
+    padding: 8px;
+    width: 100%;
+    margin-bottom: 5px;
+}
+
+.file-preview .btn-xs {
+    padding: 1px 5px;
+    font-size: 12px;
+    line-height: 1.5;
+    border-radius: 3px;
+}
+
+.file-preview .fileinput-remove {
+    top: 1px;
+    right: 1px;
+    line-height: 10px;
+}
+
+.file-preview .clickable {
+    cursor: pointer;
+}
+
+.file-preview-image {
+    font: 40px Impact, Charcoal, sans-serif;
+    color: #008000;
+}
+
+.krajee-default.file-preview-frame {
+    margin: 8px;
+    border: 1px solid rgba(0,0,0,0.2);
+    box-shadow: 0 0 10px 0 rgba(0,0,0,0.2);
+    padding: 6px;
+    float: left;
+    text-align: center;
+}
+
+.krajee-default.file-preview-frame .kv-file-content {
+    width: 213px;
+    height: 160px;
+}
+
+.krajee-default .file-preview-other-frame {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered {
+    width: 400px;
+}
+
+.krajee-default.file-preview-frame[data-template="audio"] .kv-file-content {
+    width: 240px;
+    height: 55px;
+}
+
+.krajee-default.file-preview-frame .file-thumbnail-footer {
+    height: 70px;
+}
+
+.krajee-default.file-preview-frame:not(.file-preview-error):hover {
+    border: 1px solid rgba(0,0,0,0.3);
+    box-shadow: 0 0 10px 0 rgba(0,0,0,0.4);
+}
+
+.krajee-default .file-preview-text {
+    display: block;
+    color: #428bca;
+    border: 1px solid #ddd;
+    font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+    outline: none;
+    padding: 8px;
+    resize: none;
+}
+
+.krajee-default .file-preview-html {
+    border: 1px solid #ddd;
+    padding: 8px;
+    overflow: auto;
+}
+
+.krajee-default .file-other-icon {
+    font-size: 6em;
+    line-height: 1;
+}
+
+.krajee-default .file-footer-buttons {
+    float: right;
+}
+
+.krajee-default .file-footer-caption {
+    display: block;
+    text-align: center;
+    padding-top: 4px;
+    font-size: 11px;
+    color: #777;
+    margin-bottom: 30px;
+}
+
+.file-upload-stats {
+    font-size: 10px;
+    text-align: center;
+    width: 100%;
+}
+
+.kv-upload-progress .file-upload-stats {
+    font-size: 12px;
+    margin: -10px 0 5px;
+}
+
+.krajee-default .file-preview-error {
+    opacity: 0.65;
+    box-shadow: none;
+}
+
+.krajee-default .file-thumb-progress {
+    height: 11px;
+    top: 37px;
+    left: 0;
+    right: 0;
+}
+
+.krajee-default.kvsortable-ghost {
+    background: #e1edf7;
+    border: 2px solid #a1abff;
+}
+
+.krajee-default .file-preview-other:hover {
+    opacity: 0.8;
+}
+
+.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover {
+    color: #000;
+}
+
+.kv-upload-progress .progress {
+    height: 20px;
+    margin: 10px 0;
+    overflow: hidden;
+}
+
+.kv-upload-progress .progress-bar {
+    height: 20px;
+    font-family: Verdana, Helvetica, sans-serif;
+}
+
+/*noinspection CssOverwrittenProperties*/
+.file-zoom-dialog .file-other-icon {
+    font-size: 22em;
+    font-size: 50vmin;
+}
+
+.file-zoom-dialog .modal-dialog {
+    width: auto;
+}
+
+.file-zoom-dialog .modal-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
+
+.file-zoom-dialog .btn-navigate {
+    padding: 0;
+    margin: 0;
+    background: transparent;
+    text-decoration: none;
+    outline: none;
+    opacity: 0.7;
+    top: 45%;
+    font-size: 4em;
+    color: #1c94c4;
+}
+
+.file-zoom-dialog .btn-navigate:not([disabled]):hover {
+    outline: none;
+    box-shadow: none;
+    opacity: 0.6;
+}
+
+.file-zoom-dialog .floating-buttons {
+    top: 5px;
+    right: 10px;
+}
+
+.file-zoom-dialog .btn-navigate[disabled] {
+    opacity: 0.3;
+}
+
+.file-zoom-dialog .btn-prev {
+    left: 1px;
+}
+
+.file-zoom-dialog .btn-next {
+    right: 1px;
+}
+
+.file-zoom-dialog .kv-zoom-title {
+    font-weight: 300;
+    color: #999;
+    max-width: 50%;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.file-input-new .no-browse .form-control {
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+}
+
+.file-input-ajax-new .no-browse .form-control {
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+}
+
+.file-caption-main {
+    width: 100%;
+}
+
+.file-thumb-loading {
+    background: transparent url(loading.gif) no-repeat scroll center center content-box !important;
+}
+
+.file-drop-zone {
+    border: 1px dashed #aaa;
+    border-radius: 4px;
+    text-align: center;
+    vertical-align: middle;
+    margin: 12px 15px 12px 12px;
+    padding: 5px;
+}
+
+.file-drop-zone.clickable:hover {
+    border: 2px dashed #999;
+}
+
+.file-drop-zone.clickable:focus {
+    border: 2px solid #5acde2;
+}
+
+.file-drop-zone .file-preview-thumbnails {
+    cursor: default;
+}
+
+.file-drop-zone-title {
+    color: #aaa;
+    font-size: 1.6em;
+    padding: 85px 10px;
+    cursor: default;
+}
+
+.file-highlighted {
+    border: 2px dashed #999 !important;
+    background-color: #eee;
+}
+
+.file-uploading {
+    background: url(loading-sm.gif) no-repeat center bottom 10px;
+    opacity: 0.65;
+}
+
+.file-zoom-fullscreen .modal-dialog {
+    min-width: 100%;
+    margin: 0;
+}
+
+.file-zoom-fullscreen .modal-content {
+    border-radius: 0;
+    box-shadow: none;
+    min-height: 100vh;
+}
+
+.file-zoom-fullscreen .modal-body {
+    overflow-y: auto;
+}
+
+.floating-buttons {
+    z-index: 3000;
+}
+
+.floating-buttons .btn-kv {
+    margin-left: 3px;
+    z-index: 3000;
+}
+
+.kv-zoom-actions .btn-kv {
+    margin-left: 3px;
+}
+
+.file-zoom-content {
+    height: 480px;
+    text-align: center;
+}
+
+.file-zoom-content .file-preview-image {
+    max-height: 100%;
+}
+
+.file-zoom-content .file-preview-video {
+    max-height: 100%;
+}
+
+.file-zoom-content > .file-object.type-image {
+    height: auto;
+    min-height: inherit;
+}
+
+.file-zoom-content > .file-object.type-audio {
+    width: auto;
+    height: 30px;
+}
+
+@media (min-width: 576px) {
+    .file-zoom-dialog .modal-dialog {
+        max-width: 500px;
+    }
+}
+
+@media (min-width: 992px) {
+    .file-zoom-dialog .modal-lg {
+        max-width: 800px;
+    }
+}
+
+@media (max-width: 767px) {
+    .file-preview-thumbnails {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        flex-direction: column;
+    }
+
+    .file-zoom-dialog .modal-header {
+        flex-direction: column;
+    }
+}
+
+@media (max-width: 350px) {
+    .krajee-default.file-preview-frame:not([data-template="audio"]) .kv-file-content {
+        width: 160px;
+    }
+}
+
+@media (max-width: 420px) {
+    .krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered {
+        width: 100%;
+    }
+}
+
+.file-loading[dir=rtl]:before {
+    background: transparent url(loading.gif) top right no-repeat;
+    padding-left: 0;
+    padding-right: 20px;
+}
+
+.file-sortable .file-drag-handle {
+    cursor: move;
+    opacity: 1;
+}
+
+.file-sortable .file-drag-handle:hover {
+    opacity: 0.7;
+}
+
+.clickable .file-drop-zone-title {
+    cursor: pointer;
+}
+
+.file-preview-initial.sortable-chosen {
+    background-color: #d9edf7;
+}
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js
new file mode 100644
index 0000000..5624501
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js
@@ -0,0 +1,5697 @@
+/*!
+ * bootstrap-fileinput v5.0.4
+ * http://plugins.krajee.com/file-input
+ *
+ * Author: Kartik Visweswaran
+ * Copyright: 2014 - 2019, Kartik Visweswaran, Krajee.com
+ *
+ * Licensed under the BSD-3-Clause
+ * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
+ */
+(function (factory) {
+    'use strict';
+    //noinspection JSUnresolvedVariable
+    if (typeof define === 'function' && define.amd) { // jshint ignore:line
+        // AMD. Register as an anonymous module.
+        define(['jquery'], factory); // jshint ignore:line
+    } else { // noinspection JSUnresolvedVariable
+        if (typeof module === 'object' && module.exports) { // jshint ignore:line
+            // Node/CommonJS
+            // noinspection JSUnresolvedVariable
+            module.exports = factory(require('jquery')); // jshint ignore:line
+        } else {
+            // Browser globals
+            factory(window.jQuery);
+        }
+    }
+}(function ($) {
+    'use strict';
+
+    $.fn.fileinputLocales = {};
+    $.fn.fileinputThemes = {};
+
+    String.prototype.setTokens = function (replacePairs) {
+        var str = this.toString(), key, re;
+        for (key in replacePairs) {
+            if (replacePairs.hasOwnProperty(key)) {
+                re = new RegExp('\{' + key + '\}', 'g');
+                str = str.replace(re, replacePairs[key]);
+            }
+        }
+        return str;
+    };
+
+    var $h, FileInput;
+
+    // fileinput helper object for all global variables and internal helper methods
+    //noinspection JSUnresolvedVariable
+    $h = {
+        FRAMES: '.kv-preview-thumb',
+        SORT_CSS: 'file-sortable',
+        OBJECT_PARAMS: '<param name="controller" value="true" />\n' +
+        '<param name="allowFullScreen" value="true" />\n' +
+        '<param name="allowScriptAccess" value="always" />\n' +
+        '<param name="autoPlay" value="false" />\n' +
+        '<param name="autoStart" value="false" />\n' +
+        '<param name="quality" value="high" />\n',
+        DEFAULT_PREVIEW: '<div class="file-preview-other">\n' +
+        '<span class="{previewFileIconClass}">{previewFileIcon}</span>\n' +
+        '</div>',
+        MODAL_ID: 'kvFileinputModal',
+        MODAL_EVENTS: ['show', 'shown', 'hide', 'hidden', 'loaded'],
+        logMessages: {
+            ajaxError: '{status}: {error}. Error Details: {text}.',
+            badDroppedFiles: 'Error scanning dropped files!',
+            badExifParser: 'Error loading the piexif.js library. {details}',
+            badInputType: 'The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.',
+            exifWarning: 'To avoid this warning, either set "autoOrientImage" to "false" OR ensure you have loaded ' +
+            'the "piexif.js" library correctly on your page before the "fileinput.js" script.',
+            invalidChunkSize: 'Invalid upload chunk size: "{chunkSize}". Resumable uploads are disabled.',
+            invalidThumb: 'Invalid thumb frame with id: "{id}".',
+            noResumableSupport: 'The browser does not support resumable or chunk uploads.',
+            noUploadUrl: 'The "uploadUrl" is not set. Ajax uploads and resumable uploads have been disabled.',
+            retryStatus: 'Retrying upload for chunk # {chunk} for {filename}... retry # {retry}.'
+        },
+        objUrl: window.URL || window.webkitURL,
+        now: function () {
+            return new Date();
+        },
+        round: function (num) {
+            num = parseFloat(num);
+            return isNaN(num) ? 0 : Math.floor(Math.round(num));
+        },
+        getFileRelativePath: function (file) {
+            /** @namespace file.relativePath */
+            /** @namespace file.webkitRelativePath */
+            return String(file.relativePath || file.webkitRelativePath || $h.getFileName(file) || null);
+
+        },
+        getFileId: function (file, generateFileId) {
+            var relativePath = $h.getFileRelativePath(file);
+            if (typeof generateFileId === 'function') {
+                return generateFileId(file);
+            }
+            if (!file) {
+                return null;
+            }
+            if (!relativePath) {
+                return null;
+            }
+            return (file.size + '_' + relativePath.replace(/\s/img, '_'));
+        },
+        getElapsed: function (seconds) {
+            var delta = seconds, out = '', result = {}, structure = {
+                year: 31536000,
+                month: 2592000,
+                week: 604800, // uncomment row to ignore
+                day: 86400,   // feel free to add your own row
+                hour: 3600,
+                minute: 60,
+                second: 1
+            };
+            Object.keys(structure).forEach(function (key) {
+                result[key] = Math.floor(delta / structure[key]);
+                delta -= result[key] * structure[key];
+            });
+            $.each(result, function (key, value) {
+                if (value > 0) {
+                    out += (out ? ' ' : '') + value + key.substring(0, 1);
+                }
+            });
+            return out;
+        },
+        debounce: function (func, delay) {
+            var inDebounce;
+            return function () {
+                var args = arguments, context = this;
+                clearTimeout(inDebounce);
+                inDebounce = setTimeout(function () {
+                    func.apply(context, args);
+                }, delay);
+            };
+        },
+        stopEvent: function (e) {
+            e.stopPropagation();
+            e.preventDefault();
+        },
+        getFileName: function (file) {
+            /** @namespace file.fileName */
+            return file ? (file.fileName || file.name || '') : ''; // some confusion in different versions of Firefox
+        },
+        createObjectURL: function (data) {
+            if ($h.objUrl && $h.objUrl.createObjectURL && data) {
+                return $h.objUrl.createObjectURL(data);
+            }
+            return '';
+        },
+        revokeObjectURL: function (data) {
+            if ($h.objUrl && $h.objUrl.revokeObjectURL && data) {
+                $h.objUrl.revokeObjectURL(data);
+            }
+        },
+        compare: function (input, str, exact) {
+            return input !== undefined && (exact ? input === str : input.match(str));
+        },
+        isIE: function (ver) {
+            var div, status;
+            // check for IE versions < 11
+            if (navigator.appName !== 'Microsoft Internet Explorer') {
+                return false;
+            }
+            if (ver === 10) {
+                return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent);
+            }
+            div = document.createElement('div');
+            div.innerHTML = '<!--[if IE ' + ver + ']> <i></i> <![endif]-->';
+            status = div.getElementsByTagName('i').length;
+            document.body.appendChild(div);
+            div.parentNode.removeChild(div);
+            return status;
+        },
+        canAssignFilesToInput: function () {
+            var input = document.createElement('input');
+            try {
+                input.type = 'file';
+                input.files = null;
+                return true;
+            } catch (err) {
+                return false;
+            }
+        },
+        getDragDropFolders: function (items) {
+            var i, item, len = items ? items.length : 0, folders = 0;
+            if (len > 0 && items[0].webkitGetAsEntry()) {
+                for (i = 0; i < len; i++) {
+                    item = items[i].webkitGetAsEntry();
+                    if (item && item.isDirectory) {
+                        folders++;
+                    }
+                }
+            }
+            return folders;
+        },
+        initModal: function ($modal) {
+            var $body = $('body');
+            if ($body.length) {
+                $modal.appendTo($body);
+            }
+        },
+        isEmpty: function (value, trim) {
+            return value === undefined || value === null || value.length === 0 || (trim && $.trim(value) === '');
+        },
+        isArray: function (a) {
+            return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]';
+        },
+        ifSet: function (needle, haystack, def) {
+            def = def || '';
+            return (haystack && typeof haystack === 'object' && needle in haystack) ? haystack[needle] : def;
+        },
+        cleanArray: function (arr) {
+            if (!(arr instanceof Array)) {
+                arr = [];
+            }
+            return arr.filter(function (e) {
+                return (e !== undefined && e !== null);
+            });
+        },
+        spliceArray: function (arr, index, reverseOrder) {
+            var i, j = 0, out = [], newArr;
+            if (!(arr instanceof Array)) {
+                return [];
+            }
+            newArr = $.extend(true, [], arr);
+            if (reverseOrder) {
+                newArr.reverse();
+            }
+            for (i = 0; i < newArr.length; i++) {
+                if (i !== index) {
+                    out[j] = newArr[i];
+                    j++;
+                }
+            }
+            if (reverseOrder) {
+                out.reverse();
+            }
+            return out;
+        },
+        getNum: function (num, def) {
+            def = def || 0;
+            if (typeof num === 'number') {
+                return num;
+            }
+            if (typeof num === 'string') {
+                num = parseFloat(num);
+            }
+            return isNaN(num) ? def : num;
+        },
+        hasFileAPISupport: function () {
+            return !!(window.File && window.FileReader);
+        },
+        hasDragDropSupport: function () {
+            var div = document.createElement('div');
+            /** @namespace div.draggable */
+            /** @namespace div.ondragstart */
+            /** @namespace div.ondrop */
+            return !$h.isIE(9) &&
+                (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined));
+        },
+        hasFileUploadSupport: function () {
+            return $h.hasFileAPISupport() && window.FormData;
+        },
+        hasBlobSupport: function () {
+            try {
+                return !!window.Blob && Boolean(new Blob());
+            } catch (e) {
+                return false;
+            }
+        },
+        hasArrayBufferViewSupport: function () {
+            try {
+                return new Blob([new Uint8Array(100)]).size === 100;
+            } catch (e) {
+                return false;
+            }
+        },
+        hasResumableUploadSupport: function () {
+            /** @namespace Blob.prototype.webkitSlice */
+            /** @namespace Blob.prototype.mozSlice */
+            return $h.hasFileUploadSupport() && $h.hasBlobSupport() && $h.hasArrayBufferViewSupport() &&
+                (!!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || !!Blob.prototype.slice || false);
+        },
+        dataURI2Blob: function (dataURI) {
+            //noinspection JSUnresolvedVariable
+            var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder ||
+                window.MSBlobBuilder, canBlob = $h.hasBlobSupport(), byteStr, arrayBuffer, intArray, i, mimeStr, bb,
+                canProceed = (canBlob || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array;
+            if (!canProceed) {
+                return null;
+            }
+            if (dataURI.split(',')[0].indexOf('base64') >= 0) {
+                byteStr = atob(dataURI.split(',')[1]);
+            } else {
+                byteStr = decodeURIComponent(dataURI.split(',')[1]);
+            }
+            arrayBuffer = new ArrayBuffer(byteStr.length);
+            intArray = new Uint8Array(arrayBuffer);
+            for (i = 0; i < byteStr.length; i += 1) {
+                intArray[i] = byteStr.charCodeAt(i);
+            }
+            mimeStr = dataURI.split(',')[0].split(':')[1].split(';')[0];
+            if (canBlob) {
+                return new Blob([$h.hasArrayBufferViewSupport() ? intArray : arrayBuffer], {type: mimeStr});
+            }
+            bb = new BlobBuilder();
+            bb.append(arrayBuffer);
+            return bb.getBlob(mimeStr);
+        },
+        arrayBuffer2String: function (buffer) {
+            //noinspection JSUnresolvedVariable
+            if (window.TextDecoder) {
+                // noinspection JSUnresolvedFunction
+                return new TextDecoder('utf-8').decode(buffer);
+            }
+            var array = Array.prototype.slice.apply(new Uint8Array(buffer)), out = '', i = 0, len, c, char2, char3;
+            len = array.length;
+            while (i < len) {
+                c = array[i++];
+                switch (c >> 4) { // jshint ignore:line
+                    case 0:
+                    case 1:
+                    case 2:
+                    case 3:
+                    case 4:
+                    case 5:
+                    case 6:
+                    case 7:
+                        // 0xxxxxxx
+                        out += String.fromCharCode(c);
+                        break;
+                    case 12:
+                    case 13:
+                        // 110x xxxx   10xx xxxx
+                        char2 = array[i++];
+                        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); // jshint ignore:line
+                        break;
+                    case 14:
+                        // 1110 xxxx  10xx xxxx  10xx xxxx
+                        char2 = array[i++];
+                        char3 = array[i++];
+                        out += String.fromCharCode(((c & 0x0F) << 12) | // jshint ignore:line
+                            ((char2 & 0x3F) << 6) |  // jshint ignore:line
+                            ((char3 & 0x3F) << 0)); // jshint ignore:line
+                        break;
+                }
+            }
+            return out;
+        },
+        isHtml: function (str) {
+            var a = document.createElement('div');
+            a.innerHTML = str;
+            for (var c = a.childNodes, i = c.length; i--;) {
+                if (c[i].nodeType === 1) {
+                    return true;
+                }
+            }
+            return false;
+        },
+        isSvg: function (str) {
+            return str.match(/^\s*<\?xml/i) && (str.match(/<!DOCTYPE svg/i) || str.match(/<svg/i));
+        },
+        getMimeType: function (signature, contents, type) {
+            switch (signature) {
+                case 'ffd8ffe0':
+                case 'ffd8ffe1':
+                case 'ffd8ffe2':
+                    return 'image/jpeg';
+                case '89504E47':
+                    return 'image/png';
+                case '47494638':
+                    return 'image/gif';
+                case '49492a00':
+                    return 'image/tiff';
+                case '52494646':
+                    return 'image/webp';
+                case '66747970':
+                    return 'video/3gp';
+                case '4f676753':
+                    return 'video/ogg';
+                case '1a45dfa3':
+                    return 'video/mkv';
+                case '000001ba':
+                case '000001b3':
+                    return 'video/mpeg';
+                case '3026b275':
+                    return 'video/wmv';
+                case '25504446':
+                    return 'application/pdf';
+                case '25215053':
+                    return 'application/ps';
+                case '504b0304':
+                case '504b0506':
+                case '504b0508':
+                    return 'application/zip';
+                case '377abcaf':
+                    return 'application/7z';
+                case '75737461':
+                    return 'application/tar';
+                case '7801730d':
+                    return 'application/dmg';
+                default:
+                    switch (signature.substring(0, 6)) {
+                        case '435753':
+                            return 'application/x-shockwave-flash';
+                        case '494433':
+                            return 'audio/mp3';
+                        case '425a68':
+                            return 'application/bzip';
+                        default:
+                            switch (signature.substring(0, 4)) {
+                                case '424d':
+                                    return 'image/bmp';
+                                case 'fffb':
+                                    return 'audio/mp3';
+                                case '4d5a':
+                                    return 'application/exe';
+                                case '1f9d':
+                                case '1fa0':
+                                    return 'application/zip';
+                                case '1f8b':
+                                    return 'application/gzip';
+                                default:
+                                    return contents && !contents.match(
+                                        /[^\u0000-\u007f]/) ? 'application/text-plain' : type;
+                            }
+                    }
+            }
+        },
+        addCss: function ($el, css) {
+            $el.removeClass(css).addClass(css);
+        },
+        getElement: function (options, param, value) {
+            return ($h.isEmpty(options) || $h.isEmpty(options[param])) ? value : $(options[param]);
+        },
+        uniqId: function () {
+            return Math.round(new Date().getTime()) + '_' + Math.round(Math.random() * 100);
+        },
+        htmlEncode: function (str, undefVal) {
+            if (str === undefined) {
+                return undefVal || null;
+            }
+            return str.replace(/&/g, '&amp;')
+                .replace(/</g, '&lt;')
+                .replace(/>/g, '&gt;')
+                .replace(/"/g, '&quot;')
+                .replace(/'/g, '&apos;');
+        },
+        replaceTags: function (str, tags) {
+            var out = str;
+            if (!tags) {
+                return out;
+            }
+            $.each(tags, function (key, value) {
+                if (typeof value === 'function') {
+                    value = value();
+                }
+                out = out.split(key).join(value);
+            });
+            return out;
+        },
+        cleanMemory: function ($thumb) {
+            var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src');
+            $h.revokeObjectURL(data);
+        },
+        findFileName: function (filePath) {
+            var sepIndex = filePath.lastIndexOf('/');
+            if (sepIndex === -1) {
+                sepIndex = filePath.lastIndexOf('\\');
+            }
+            return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop();
+        },
+        checkFullScreen: function () {
+            //noinspection JSUnresolvedVariable
+            return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement ||
+                document.msFullscreenElement;
+        },
+        toggleFullScreen: function (maximize) {
+            var doc = document, de = doc.documentElement;
+            if (de && maximize && !$h.checkFullScreen()) {
+                /** @namespace document.requestFullscreen */
+                /** @namespace document.msRequestFullscreen */
+                /** @namespace document.mozRequestFullScreen */
+                /** @namespace document.webkitRequestFullscreen */
+                /** @namespace Element.ALLOW_KEYBOARD_INPUT */
+                if (de.requestFullscreen) {
+                    de.requestFullscreen();
+                } else {
+                    if (de.msRequestFullscreen) {
+                        de.msRequestFullscreen();
+                    } else {
+                        if (de.mozRequestFullScreen) {
+                            de.mozRequestFullScreen();
+                        } else {
+                            if (de.webkitRequestFullscreen) {
+                                de.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+                            }
+                        }
+                    }
+                }
+            } else {
+                /** @namespace document.exitFullscreen */
+                /** @namespace document.msExitFullscreen */
+                /** @namespace document.mozCancelFullScreen */
+                /** @namespace document.webkitExitFullscreen */
+                if (doc.exitFullscreen) {
+                    doc.exitFullscreen();
+                } else {
+                    if (doc.msExitFullscreen) {
+                        doc.msExitFullscreen();
+                    } else {
+                        if (doc.mozCancelFullScreen) {
+                            doc.mozCancelFullScreen();
+                        } else {
+                            if (doc.webkitExitFullscreen) {
+                                doc.webkitExitFullscreen();
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        moveArray: function (arr, oldIndex, newIndex, reverseOrder) {
+            var newArr = $.extend(true, [], arr);
+            if (reverseOrder) {
+                newArr.reverse();
+            }
+            if (newIndex >= newArr.length) {
+                var k = newIndex - newArr.length;
+                while ((k--) + 1) {
+                    newArr.push(undefined);
+                }
+            }
+            newArr.splice(newIndex, 0, newArr.splice(oldIndex, 1)[0]);
+            if (reverseOrder) {
+                newArr.reverse();
+            }
+            return newArr;
+        },
+        cleanZoomCache: function ($el) {
+            var $cache = $el.closest('.kv-zoom-cache-theme');
+            if (!$cache.length) {
+                $cache = $el.closest('.kv-zoom-cache');
+            }
+            $cache.remove();
+        },
+        closeButton: function (css) {
+            css = css ? 'close ' + css : 'close';
+            return '<button type="button" class="' + css + '" aria-label="Close">\n' +
+                '  <span aria-hidden="true">&times;</span>\n' +
+                '</button>';
+        },
+        getRotation: function (value) {
+            switch (value) {
+                case 2:
+                    return 'rotateY(180deg)';
+                case 3:
+                    return 'rotate(180deg)';
+                case 4:
+                    return 'rotate(180deg) rotateY(180deg)';
+                case 5:
+                    return 'rotate(270deg) rotateY(180deg)';
+                case 6:
+                    return 'rotate(90deg)';
+                case 7:
+                    return 'rotate(90deg) rotateY(180deg)';
+                case 8:
+                    return 'rotate(270deg)';
+                default:
+                    return '';
+            }
+        },
+        setTransform: function (el, val) {
+            if (!el) {
+                return;
+            }
+            el.style.transform = val;
+            el.style.webkitTransform = val;
+            el.style['-moz-transform'] = val;
+            el.style['-ms-transform'] = val;
+            el.style['-o-transform'] = val;
+        }
+    };
+    FileInput = function (element, options) {
+        var self = this;
+        self.$element = $(element);
+        self.$parent = self.$element.parent();
+        if (!self._validate()) {
+            return;
+        }
+        self.isPreviewable = $h.hasFileAPISupport();
+        self.isIE9 = $h.isIE(9);
+        self.isIE10 = $h.isIE(10);
+        if (self.isPreviewable || self.isIE9) {
+            self._init(options);
+            self._listen();
+        }
+        self.$element.removeClass('file-loading');
+    };
+    //noinspection JSUnusedGlobalSymbols
+    FileInput.prototype = {
+        constructor: FileInput,
+        _cleanup: function () {
+            var self = this;
+            self.reader = null;
+            self.clearFileStack();
+            self.fileBatchCompleted = true;
+            self.isError = false;
+            self.cancelling = false;
+            self.paused = false;
+            self.lastProgress = 0;
+            self._initAjax();
+        },
+        _initAjax: function () {
+            var self = this;
+            self.ajaxQueue = [];
+            self.ajaxRequests = [];
+            self.ajaxQueueIntervalId = null;
+            self.ajaxCurrentThreads = 0;
+            self.ajaxAborted = false;
+        },
+        _init: function (options, refreshMode) {
+            var self = this, f, $el = self.$element, $cont, t, tmp;
+            self.options = options;
+            $.each(options, function (key, value) {
+                switch (key) {
+                    case 'minFileCount':
+                    case 'maxFileCount':
+                    case 'minFileSize':
+                    case 'maxFileSize':
+                    case 'maxFilePreviewSize':
+                    case 'resizeImageQuality':
+                    case 'resizeIfSizeMoreThan':
+                    case 'progressUploadThreshold':
+                    case 'initialPreviewCount':
+                    case 'zoomModalHeight':
+                    case 'minImageHeight':
+                    case 'maxImageHeight':
+                    case 'minImageWidth':
+                    case 'maxImageWidth':
+                        self[key] = $h.getNum(value);
+                        break;
+                    default:
+                        self[key] = value;
+                        break;
+                }
+            });
+            if (self.rtl) { // swap buttons for rtl
+                tmp = self.previewZoomButtonIcons.prev;
+                self.previewZoomButtonIcons.prev = self.previewZoomButtonIcons.next;
+                self.previewZoomButtonIcons.next = tmp;
+            }
+            // validate chunk threads to not exceed maxAjaxThreads
+            if (!isNaN(self.maxAjaxThreads) && self.maxAjaxThreads < self.resumableUploadOptions.maxThreads) {
+                self.resumableUploadOptions.maxThreads = self.maxAjaxThreads;
+            }
+            self._initFileManager();
+            if (typeof self.autoOrientImage === 'function') {
+                self.autoOrientImage = self.autoOrientImage();
+            }
+            if (typeof self.autoOrientImageInitial === 'function') {
+                self.autoOrientImageInitial = self.autoOrientImageInitial();
+            }
+            if (!refreshMode) {
+                self._cleanup();
+            }
+            self.$form = $el.closest('form');
+            self._initTemplateDefaults();
+            self.uploadFileAttr = !$h.isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data';
+            t = self._getLayoutTemplate('progress');
+            self.progressTemplate = t.replace('{class}', self.progressClass);
+            self.progressInfoTemplate = t.replace('{class}', self.progressInfoClass);
+            self.progressPauseTemplate = t.replace('{class}', self.progressPauseClass);
+            self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
+            self.progressErrorTemplate = t.replace('{class}', self.progressErrorClass);
+            self.isDisabled = $el.attr('disabled') || $el.attr('readonly');
+            if (self.isDisabled) {
+                $el.attr('disabled', true);
+            }
+            self.isClickable = self.browseOnZoneClick && self.showPreview &&
+                (self.dropZoneEnabled || !$h.isEmpty(self.defaultPreviewContent));
+            self.isAjaxUpload = $h.hasFileUploadSupport() && !$h.isEmpty(self.uploadUrl);
+            self.dropZoneEnabled = $h.hasDragDropSupport() && self.dropZoneEnabled;
+            if (!self.isAjaxUpload) {
+                self.dropZoneEnabled = self.dropZoneEnabled && $h.canAssignFilesToInput();
+            }
+            self.slug = typeof options.slugCallback === 'function' ? options.slugCallback : self._slugDefault;
+            self.mainTemplate = self.showCaption ? self._getLayoutTemplate('main1') : self._getLayoutTemplate('main2');
+            self.captionTemplate = self._getLayoutTemplate('caption');
+            self.previewGenericTemplate = self._getPreviewTemplate('generic');
+            if (!self.imageCanvas && self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) {
+                self.imageCanvas = document.createElement('canvas');
+                self.imageCanvasContext = self.imageCanvas.getContext('2d');
+            }
+            if ($h.isEmpty($el.attr('id'))) {
+                $el.attr('id', $h.uniqId());
+            }
+            self.namespace = '.fileinput_' + $el.attr('id').replace(/-/g, '_');
+            if (self.$container === undefined) {
+                self.$container = self._createContainer();
+            } else {
+                self._refreshContainer();
+            }
+            $cont = self.$container;
+            self.$dropZone = $cont.find('.file-drop-zone');
+            self.$progress = $cont.find('.kv-upload-progress');
+            self.$btnUpload = $cont.find('.fileinput-upload');
+            self.$captionContainer = $h.getElement(options, 'elCaptionContainer', $cont.find('.file-caption'));
+            self.$caption = $h.getElement(options, 'elCaptionText', $cont.find('.file-caption-name'));
+            if (!$h.isEmpty(self.msgPlaceholder)) {
+                f = $el.attr('multiple') ? self.filePlural : self.fileSingle;
+                self.$caption.attr('placeholder', self.msgPlaceholder.replace('{files}', f));
+            }
+            self.$captionIcon = self.$captionContainer.find('.file-caption-icon');
+            self.$previewContainer = $h.getElement(options, 'elPreviewContainer', $cont.find('.file-preview'));
+            self.$preview = $h.getElement(options, 'elPreviewImage', $cont.find('.file-preview-thumbnails'));
+            self.$previewStatus = $h.getElement(options, 'elPreviewStatus', $cont.find('.file-preview-status'));
+            self.$errorContainer = $h.getElement(options, 'elErrorContainer',
+                self.$previewContainer.find('.kv-fileinput-error'));
+            self._validateDisabled();
+            if (!$h.isEmpty(self.msgErrorClass)) {
+                $h.addCss(self.$errorContainer, self.msgErrorClass);
+            }
+            if (!refreshMode) {
+                self.$errorContainer.hide();
+                self.previewInitId = 'preview-' + $h.uniqId();
+                self._initPreviewCache();
+                self._initPreview(true);
+                self._initPreviewActions();
+                if (self.$parent.hasClass('file-loading')) {
+                    self.$container.insertBefore(self.$parent);
+                    self.$parent.remove();
+                }
+            } else {
+                if (!self._errorsExist()) {
+                    self.$errorContainer.hide();
+                }
+            }
+            self._setFileDropZoneTitle();
+            if ($el.attr('disabled')) {
+                self.disable();
+            }
+            self._initZoom();
+            if (self.hideThumbnailContent) {
+                $h.addCss(self.$preview, 'hide-content');
+            }
+        },
+        _initFileManager: function () {
+            var self = this;
+            self.fileManager = {
+                stack: {},
+                processed: [],
+                errors: [],
+                loadedImages: {},
+                totalImages: 0,
+                totalFiles: null,
+                totalSize: null,
+                uploadedSize: 0,
+                stats: {},
+                initStats: function (id) {
+                    var data = {started: $h.now().getTime()};
+                    if (id) {
+                        self.fileManager.stats[id] = data;
+                    } else {
+                        self.fileManager.stats = data;
+                    }
+                },
+                getUploadStats: function (id, loaded, total) {
+                    var fm = self.fileManager, started = id ? fm.stats[id] && fm.stats[id].started || null : null;
+                    if (!started) {
+                        started = $h.now().getTime();
+                    }
+                    var elapsed = ($h.now().getTime() - started) / 1000,
+                        speeds = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s'],
+                        bps = elapsed ? loaded / elapsed : 0, bitrate = self._getSize(bps, speeds),
+                        pendingBytes = total - loaded,
+                        out = {
+                            fileId: id,
+                            started: started,
+                            elapsed: elapsed,
+                            loaded: loaded,
+                            total: total,
+                            bps: bps,
+                            bitrate: bitrate,
+                            pendingBytes: pendingBytes
+                        };
+                    if (id) {
+                        fm.stats[id] = out;
+                    } else {
+                        fm.stats = out;
+                    }
+                    return out;
+                },
+                exists: function (id) {
+                    return $.inArray(id, self.fileManager.getIdList()) !== -1;
+                },
+                count: function () {
+                    return self.fileManager.getIdList().length;
+                },
+                total: function () {
+                    var fm = self.fileManager;
+                    if (!fm.totalFiles) {
+                        fm.totalFiles = fm.count();
+                    }
+                    return fm.totalFiles;
+                },
+                getTotalSize: function () {
+                    var fm = self.fileManager;
+                    if (fm.totalSize) {
+                        return fm.totalSize;
+                    }
+                    fm.totalSize = 0;
+                    $.each(self.fileManager.stack, function (id, f) {
+                        var size = parseFloat(f.size);
+                        fm.totalSize += isNaN(size) ? 0 : size;
+                    });
+                    return fm.totalSize;
+                },
+                add: function (file, id) {
+                    if (!id) {
+                        id = self.fileManager.getId(file);
+                    }
+                    if (!id) {
+                        return;
+                    }
+                    self.fileManager.stack[id] = {
+                        file: file,
+                        name: $h.getFileName(file),
+                        relativePath: $h.getFileRelativePath(file),
+                        size: file.size,
+                        nameFmt: self._getFileName(file, ''),
+                        sizeFmt: self._getSize(file.size)
+                    };
+                },
+                remove: function ($thumb) {
+                    var id = $thumb.attr('data-fileid');
+                    if (id) {
+                        self.fileManager.removeFile(id);
+                    }
+                },
+                removeFile: function (id) {
+                    delete self.fileManager.stack[id];
+                    delete self.fileManager.loadedImages[id];
+                },
+                move: function (idFrom, idTo) {
+                    var result = {}, stack = self.fileManager.stack;
+                    if (!idFrom && !idTo || idFrom === idTo) {
+                        return;
+                    }
+                    $.each(stack, function (k, v) {
+                        if (k !== idFrom) {
+                            result[k] = v;
+                        }
+                        if (k === idTo) {
+                            result[idFrom] = stack[idFrom];
+                        }
+                    });
+                    self.fileManager.stack = result;
+                },
+                list: function () {
+                    var files = [];
+                    $.each(self.fileManager.stack, function (k, v) {
+                        if (v && v.file) {
+                            files.push(v.file);
+                        }
+                    });
+                    return files;
+                },
+                isPending: function (id) {
+                    return $.inArray(id, self.fileManager.processed) === -1 && self.fileManager.exists(id);
+                },
+                isProcessed: function () {
+                    var processed = true, fm = self.fileManager;
+                    $.each(fm.stack, function (id) {
+                        if (fm.isPending(id)) {
+                            processed = false;
+                        }
+                    });
+                    return processed;
+                },
+                clear: function () {
+                    var fm = self.fileManager;
+                    fm.totalFiles = null;
+                    fm.totalSize = null;
+                    fm.uploadedSize = 0;
+                    fm.stack = {};
+                    fm.errors = [];
+                    fm.processed = [];
+                    fm.stats = {};
+                    fm.clearImages();
+                },
+                clearImages: function () {
+                    self.fileManager.loadedImages = {};
+                    self.fileManager.totalImages = 0;
+                },
+                addImage: function (id, config) {
+                    self.fileManager.loadedImages[id] = config;
+                },
+                removeImage: function (id) {
+                    delete self.fileManager.loadedImages[id];
+                },
+                getImageIdList: function () {
+                    return Object.keys(self.fileManager.loadedImages);
+                },
+                getImageCount: function () {
+                    return self.fileManager.getImageIdList().length;
+                },
+                getId: function (file) {
+                    return self._getFileId(file);
+                },
+                getIndex: function (id) {
+                    return self.fileManager.getIdList().indexOf(id);
+                },
+                getThumb: function (id) {
+                    var $thumb = null;
+                    self._getThumbs().each(function () {
+                        if ($(this).attr('data-fileid') === id) {
+                            $thumb = $(this);
+                        }
+                    });
+                    return $thumb;
+                },
+                getThumbIndex: function ($thumb) {
+                    var id = $thumb.attr('data-fileid');
+                    return self.fileManager.getIndex(id);
+                },
+                getIdList: function () {
+                    return Object.keys(self.fileManager.stack);
+                },
+                getFile: function (id) {
+                    return self.fileManager.stack[id] || null;
+                },
+                getFileName: function (id, fmt) {
+                    var file = self.fileManager.getFile(id);
+                    if (!file) {
+                        return '';
+                    }
+                    return fmt ? (file.nameFmt || '') : file.name || '';
+                },
+                getFirstFile: function () {
+                    var ids = self.fileManager.getIdList(), id = ids && ids.length ? ids[0] : null;
+                    return self.fileManager.getFile(id);
+                },
+                setFile: function (id, file) {
+                    if (self.fileManager.getFile(id)) {
+                        self.fileManager.stack[id].file = file;
+                    } else {
+                        self.fileManager.add(file, id);
+                    }
+                },
+                setProcessed: function (id) {
+                    self.fileManager.processed.push(id);
+                },
+                getProgress: function () {
+                    var total = self.fileManager.total(), processed = self.fileManager.processed.length;
+                    if (!total) {
+                        return 0;
+                    }
+                    return Math.ceil(processed / total * 100);
+
+                },
+                setProgress: function (id, pct) {
+                    var f = self.fileManager.getFile(id);
+                    if (!isNaN(pct) && f) {
+                        f.progress = pct;
+                    }
+                }
+            };
+        },
+        _setUploadData: function (fd, config) {
+            var self = this;
+            $.each(config, function (key, value) {
+                var param = self.uploadParamNames[key] || key;
+                if ($h.isArray(value)) {
+                    fd.append(param, value[0], value[1]);
+                } else {
+                    fd.append(param, value);
+                }
+            });
+        },
+        _initResumableUpload: function () {
+            var self = this, opts = self.resumableUploadOptions, logs = $h.logMessages;
+            if (!self.enableResumableUpload) {
+                return;
+            }
+            if (opts.fallback !== false && typeof opts.fallback !== 'function') {
+                opts.fallback = function (s) {
+                    s._log(logs.noResumableSupport);
+                    s.enableResumableUpload = false;
+                };
+            }
+            if (!$h.hasResumableUploadSupport() && opts.fallback !== false) {
+                opts.fallback(self);
+                return;
+            }
+            if (!self.uploadUrl && self.enableResumableUpload) {
+                self._log(logs.noUploadUrl);
+                self.enableResumableUpload = false;
+                return;
+
+            }
+            opts.chunkSize = parseFloat(opts.chunkSize);
+            if (opts.chunkSize <= 0 || isNaN(opts.chunkSize)) {
+                self._log(logs.invalidChunkSize, {chunkSize: opts.chunkSize});
+                self.enableResumableUpload = false;
+                return;
+            }
+            self.resumableManager = {
+                init: function (id, f, index) {
+                    var rm = self.resumableManager, fm = self.fileManager;
+                    rm.currThreads = 0;
+                    rm.logs = [];
+                    rm.stack = [];
+                    rm.error = '';
+                    rm.chunkIntervalId = null;
+                    rm.id = id;
+                    rm.file = f.file;
+                    rm.fileName = f.name;
+                    rm.fileIndex = index;
+                    rm.completed = false;
+                    rm.testing = false;
+                    rm.lastProgress = 0;
+                    if (self.showPreview) {
+                        rm.$thumb = fm.getThumb(id) || null;
+                        rm.$progress = rm.$btnDelete = null;
+                        if (rm.$thumb && rm.$thumb.length) {
+                            rm.$progress = rm.$thumb.find('.file-thumb-progress');
+                            rm.$btnDelete = rm.$thumb.find('.kv-file-remove');
+                        }
+                    }
+                    rm.chunkSize = self.resumableUploadOptions.chunkSize * 1024;
+                    rm.chunkCount = rm.getTotalChunks();
+                },
+                logAjaxError: function (jqXHR, textStatus, errorThrown) {
+                    if (self.resumableUploadOptions.showErrorLog) {
+                        self._log(logs.ajaxError, {
+                            status: jqXHR.status,
+                            error: errorThrown,
+                            text: jqXHR.responseText || ''
+                        });
+                    }
+                },
+                reset: function () {
+                    var rm = self.resumableManager;
+                    rm.processed = {};
+                },
+                setProcessed: function (status) {
+                    var rm = self.resumableManager, fm = self.fileManager, id = rm.id, msg,
+                        $thumb = rm.$thumb, $prog = rm.$progress, hasThumb = $thumb && $thumb.length,
+                        params = {id: hasThumb ? $thumb.attr('id') : '', index: fm.getIndex(id), fileId: id};
+                    rm.completed = true;
+                    rm.lastProgress = 0;
+                    fm.uploadedSize += rm.file.size;
+                    if (hasThumb) {
+                        $thumb.removeClass('file-uploading');
+                    }
+                    if (status === 'success') {
+                        if (self.showPreview) {
+                            self._setProgress(101, $prog);
+                            self._setThumbStatus($thumb, 'Success');
+                            self._initUploadSuccess(rm.processed[id].data, $thumb);
+                        }
+                        self.fileManager.removeFile(id);
+                        delete rm.processed[id];
+                        self._raise('fileuploaded', [params.id, params.index, params.fileId]);
+                        if (fm.isProcessed()) {
+                            self._setProgress(101);
+                        }
+                    } else {
+                        if (self.showPreview) {
+                            self._setThumbStatus($thumb, 'Error');
+                            self._setPreviewError($thumb, true);
+                            self._setProgress(101, $prog, self.msgProgressError);
+                            self._setProgress(101, self.$progress, self.msgProgressError);
+                            self.cancelling = true;
+                        }
+                        if (!self.$errorContainer.find('li[data-file-id="' + params.fileId + '"]').length) {
+                            msg = self.msgResumableUploadRetriesExceeded.setTokens({
+                                file: rm.fileName,
+                                max: self.resumableUploadOptions.maxRetries,
+                                error: rm.error
+                            });
+                            self._showFileError(msg, params);
+                        }
+                    }
+                    if (fm.isProcessed()) {
+                        rm.reset();
+                    }
+                },
+                check: function () {
+                    var rm = self.resumableManager, status = true;
+                    $.each(rm.logs, function (index, value) {
+                        if (!value) {
+                            status = false;
+                            return false;
+                        }
+                    });
+                    if (status) {
+                        clearInterval(rm.chunkIntervalId);
+                        rm.setProcessed('success');
+                    }
+                },
+                processedResumables: function () {
+                    var logs = self.resumableManager.logs, i, count = 0;
+                    if (!logs || !logs.length) {
+                        return 0;
+                    }
+                    for (i = 0; i < logs.length; i++) {
+                        if (logs[i] === true) {
+                            count++;
+                        }
+                    }
+                    return count;
+                },
+                getUploadedSize: function () {
+                    var rm = self.resumableManager, size = rm.processedResumables() * rm.chunkSize;
+                    return size > rm.file.size ? rm.file.size : size;
+                },
+                getTotalChunks: function () {
+                    var rm = self.resumableManager, chunkSize = parseFloat(rm.chunkSize);
+                    if (!isNaN(chunkSize) && chunkSize > 0) {
+                        return Math.ceil(rm.file.size / chunkSize);
+                    }
+                    return 0;
+                },
+                getProgress: function () {
+                    var rm = self.resumableManager, processed = rm.processedResumables(), total = rm.chunkCount;
+                    if (total === 0) {
+                        return 0;
+                    }
+                    return Math.ceil(processed / total * 100);
+                },
+                checkAborted: function (intervalId) {
+                    if (self.paused || self.cancelling) {
+                        clearInterval(intervalId);
+                        self.unlock();
+                    }
+                },
+                upload: function () {
+                    var rm = self.resumableManager, fm = self.fileManager, ids = fm.getIdList(), flag = 'new',
+                        intervalId;
+                    intervalId = setInterval(function () {
+                        var id;
+                        rm.checkAborted(intervalId);
+                        if (flag === 'new') {
+                            self.lock();
+                            flag = 'processing';
+                            id = ids.shift();
+                            fm.initStats(id);
+                            if (fm.stack[id]) {
+                                rm.init(id, fm.stack[id], fm.getIndex(id));
+                                rm.testUpload();
+                                rm.uploadResumable();
+                            }
+                        }
+                        if (!fm.isPending(id) && rm.completed) {
+                            flag = 'new';
+                        }
+                        if (fm.isProcessed()) {
+                            var $initThumbs = self.$preview.find('.file-preview-initial');
+                            if ($initThumbs.length) {
+                                $h.addCss($initThumbs, $h.SORT_CSS);
+                                self._initSortable();
+                            }
+                            clearInterval(intervalId);
+                            self._clearFileInput();
+                            self.unlock();
+                            setTimeout(function () {
+                                var data = self.previewCache.data;
+                                if (data) {
+                                    self.initialPreview = data.content;
+                                    self.initialPreviewConfig = data.config;
+                                    self.initialPreviewThumbTags = data.tags;
+                                }
+                                self._raise('filebatchuploadcomplete', [
+                                    self.initialPreview,
+                                    self.initialPreviewConfig,
+                                    self.initialPreviewThumbTags,
+                                    self._getExtraData()
+                                ]);
+                            }, self.processDelay);
+                        }
+                    }, self.processDelay);
+                },
+                uploadResumable: function () {
+                    var i, rm = self.resumableManager, total = rm.chunkCount;
+                    for (i = 0; i < total; i++) {
+                        rm.logs[i] = !!(rm.processed[rm.id] && rm.processed[rm.id][i]);
+                    }
+                    for (i = 0; i < total; i++) {
+                        rm.pushAjax(i, 0);
+                    }
+                    rm.chunkIntervalId = setInterval(rm.loopAjax, self.queueDelay);
+                },
+                testUpload: function () {
+                    var rm = self.resumableManager, opts = self.resumableUploadOptions, fd, f,
+                        fm = self.fileManager, id = rm.id, fnBefore, fnSuccess, fnError, fnComplete, outData;
+                    if (!opts.testUrl) {
+                        rm.testing = false;
+                        return;
+                    }
+                    rm.testing = true;
+                    fd = new FormData();
+                    f = fm.stack[id];
+                    self._setUploadData(fd, {
+                        fileId: id,
+                        fileName: f.fileName,
+                        fileSize: f.size,
+                        fileRelativePath: f.relativePath,
+                        chunkSize: rm.chunkSize,
+                        chunkCount: rm.chunkCount
+                    });
+                    fnBefore = function (jqXHR) {
+                        outData = self._getOutData(fd, jqXHR);
+                        self._raise('filetestbeforesend', [id, fm, rm, outData]);
+                    };
+                    fnSuccess = function (data, textStatus, jqXHR) {
+                        outData = self._getOutData(fd, jqXHR, data);
+                        var pNames = self.uploadParamNames, chunksUploaded = pNames.chunksUploaded || 'chunksUploaded',
+                            params = [id, fm, rm, outData];
+                        if (!data[chunksUploaded] || !$h.isArray(data[chunksUploaded])) {
+                            self._raise('filetesterror', params);
+                        } else {
+                            if (!rm.processed[id]) {
+                                rm.processed[id] = {};
+                            }
+                            $.each(data[chunksUploaded], function (key, index) {
+                                rm.logs[index] = true;
+                                rm.processed[id][index] = true;
+                            });
+                            rm.processed[id].data = data;
+                            self._raise('filetestsuccess', params);
+                        }
+                        rm.testing = false;
+                    };
+                    fnError = function (jqXHR, textStatus, errorThrown) {
+                        outData = self._getOutData(fd, jqXHR);
+                        self._raise('filetestajaxerror', [id, fm, rm, outData]);
+                        rm.logAjaxError(jqXHR, textStatus, errorThrown);
+                        rm.testing = false;
+                    };
+                    fnComplete = function () {
+                        self._raise('filetestcomplete', [id, fm, rm, self._getOutData(fd)]);
+                        rm.testing = false;
+                    };
+                    self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex, opts.testUrl);
+                },
+                pushAjax: function (index, retry) {
+                    self.resumableManager.stack.push([index, retry]);
+                },
+                sendAjax: function (index, retry) {
+                    var fm = self.fileManager, rm = self.resumableManager, opts = self.resumableUploadOptions, f,
+                        chunkSize = rm.chunkSize, id = rm.id, file = rm.file, $thumb = rm.$thumb,
+                        $btnDelete = rm.$btnDelete;
+                    if (rm.processed[id] && rm.processed[id][index]) {
+                        return;
+                    }
+                    rm.currThreads++;
+                    if (retry > opts.maxRetries) {
+                        rm.setProcessed('error');
+                        return;
+                    }
+                    var fd, outData, fnBefore, fnSuccess, fnError, fnComplete, slice = file.slice ? 'slice' :
+                        (file.mozSlice ? 'mozSlice' : (file.webkitSlice ? 'webkitSlice' : 'slice')),
+                        blob = file[slice](chunkSize * index, chunkSize * (index + 1));
+                    fd = new FormData();
+                    f = fm.stack[id];
+                    self._setUploadData(fd, {
+                        chunkCount: rm.chunkCount,
+                        chunkIndex: index,
+                        chunkSize: chunkSize,
+                        chunkSizeStart: chunkSize * index,
+                        fileBlob: [blob, rm.fileName],
+                        fileId: id,
+                        fileName: rm.fileName,
+                        fileRelativePath: f.relativePath,
+                        fileSize: file.size,
+                        retryCount: retry
+                    });
+                    if (rm.$progress && rm.$progress.length) {
+                        rm.$progress.show();
+                    }
+                    fnBefore = function (jqXHR) {
+                        outData = self._getOutData(fd, jqXHR);
+                        if (self.showPreview) {
+                            if (!$thumb.hasClass('file-preview-success')) {
+                                self._setThumbStatus($thumb, 'Loading');
+                                $h.addCss($thumb, 'file-uploading');
+                            }
+                            $btnDelete.attr('disabled', true);
+                        }
+                        self._raise('filechunkbeforesend', [id, index, retry, fm, rm, outData]);
+                    };
+                    fnSuccess = function (data, textStatus, jqXHR) {
+                        outData = self._getOutData(fd, jqXHR, data);
+                        var paramNames = self.uploadParamNames, chunkIndex = paramNames.chunkIndex || 'chunkIndex',
+                            opts = self.resumableUploadOptions, params = [id, index, retry, fm, rm, outData];
+                        rm.currThreads--;
+                        if (data.error) {
+                            if (opts.showErrorLog) {
+                                self._log(logs.retryStatus, {
+                                    retry: retry + 1,
+                                    filename: rm.fileName,
+                                    chunk: index
+                                });
+                            }
+                            rm.pushAjax(index, retry + 1);
+                            rm.error = data.error;
+                            self._raise('filechunkerror', params);
+                        } else {
+                            rm.logs[data[chunkIndex]] = true;
+                            if (!rm.processed[id]) {
+                                rm.processed[id] = {};
+                            }
+                            rm.processed[id][data[chunkIndex]] = true;
+                            rm.processed[id].data = data;
+                            self._raise('filechunksuccess', params);
+                            rm.check();
+                        }
+                    };
+                    fnError = function (jqXHR, textStatus, errorThrown) {
+                        outData = self._getOutData(fd, jqXHR);
+                        rm.currThreads--;
+                        rm.error = errorThrown;
+                        rm.logAjaxError(jqXHR, textStatus, errorThrown);
+                        self._raise('filechunkajaxerror', [id, index, retry, fm, rm, outData]);
+                        rm.pushAjax(index, retry + 1);
+                    };
+                    fnComplete = function () {
+                        self._raise('filechunkcomplete', [id, index, retry, fm, rm, self._getOutData(fd)]);
+                    };
+                    self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex);
+                },
+                loopAjax: function () {
+                    var rm = self.resumableManager;
+                    if (rm.currThreads < self.resumableUploadOptions.maxThreads && !rm.testing) {
+                        var arr = rm.stack.shift(), index;
+                        if (typeof arr !== 'undefined') {
+                            index = arr[0];
+                            if (!rm.processed[rm.id] || !rm.processed[rm.id][index]) {
+                                rm.sendAjax(index, arr[1]);
+                            } else {
+                                if (rm.processedResumables() >= rm.getTotalChunks()) {
+                                    rm.setProcessed('success');
+                                    clearInterval(rm.chunkIntervalId);
+                                }
+                            }
+                        }
+                    }
+                }
+            };
+            self.resumableManager.reset();
+        },
+        _initTemplateDefaults: function () {
+            var self = this, tMain1, tMain2, tPreview, tFileIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse,
+                tModalMain, tModal, tProgress, tSize, tFooter, tActions, tActionDelete, tActionUpload, tActionDownload,
+                tActionZoom, tActionDrag, tIndicator, tTagBef, tTagBef1, tTagBef2, tTagAft, tGeneric, tHtml, tImage,
+                tText, tOffice, tGdocs, tVideo, tAudio, tFlash, tObject, tPdf, tOther, tStyle, tZoomCache, vDefaultDim,
+                tStats;
+            tMain1 = '{preview}\n' +
+                '<div class="kv-upload-progress kv-hidden"></div><div class="clearfix"></div>\n' +
+                '<div class="input-group {class}">\n' +
+                '  {caption}\n' +
+                '<div class="input-group-btn input-group-append">\n' +
+                '      {remove}\n' +
+                '      {cancel}\n' +
+                '      {pause}\n' +
+                '      {upload}\n' +
+                '      {browse}\n' +
+                '    </div>\n' +
+                '</div>';
+            tMain2 = '{preview}\n<div class="kv-upload-progress kv-hidden"></div>\n<div class="clearfix"></div>\n' +
+                '{remove}\n{cancel}\n{upload}\n{browse}\n';
+            tPreview = '<div class="file-preview {class}">\n' +
+                '    {close}' +
+                '    <div class="{dropClass}">\n' +
+                '    <div class="file-preview-thumbnails">\n' +
+                '    </div>\n' +
+                '    <div class="clearfix"></div>' +
+                '    <div class="file-preview-status text-center text-success"></div>\n' +
+                '    <div class="kv-fileinput-error"></div>\n' +
+                '    </div>\n' +
+                '</div>';
+            tClose = $h.closeButton('fileinput-remove');
+            tFileIcon = '<i class="glyphicon glyphicon-file"></i>';
+            // noinspection HtmlUnknownAttribute
+            tCaption = '<div class="file-caption form-control {class}" tabindex="500">\n' +
+                '  <span class="file-caption-icon"></span>\n' +
+                '  <input class="file-caption-name" onkeydown="return false;" onpaste="return false;">\n' +
+                '</div>';
+            //noinspection HtmlUnknownAttribute
+            tBtnDefault = '<button type="{type}" tabindex="500" title="{title}" class="{css}" ' +
+                '{status}>{icon} {label}</button>';
+            //noinspection HtmlUnknownAttribute
+            tBtnLink = '<a href="{href}" tabindex="500" title="{title}" class="{css}" {status}>{icon} {label}</a>';
+            //noinspection HtmlUnknownAttribute
+            tBtnBrowse = '<div tabindex="500" class="{css}" {status}>{icon} {label}</div>';
+            tModalMain = '<div id="' + $h.MODAL_ID + '" class="file-zoom-dialog modal fade" ' +
+                'tabindex="-1" aria-labelledby="' + $h.MODAL_ID + 'Label"></div>';
+            tModal = '<div class="modal-dialog modal-lg{rtl}" role="document">\n' +
+                '  <div class="modal-content">\n' +
+                '    <div class="modal-header">\n' +
+                '      <h5 class="modal-title">{heading}</h5>\n' +
+                '      <span class="kv-zoom-title"></span>\n' +
+                '      <div class="kv-zoom-actions">{toggleheader}{fullscreen}{borderless}{close}</div>\n' +
+                '    </div>\n' +
+                '    <div class="modal-body">\n' +
+                '      <div class="floating-buttons"></div>\n' +
+                '      <div class="kv-zoom-body file-zoom-content {zoomFrameClass}"></div>\n' + '{prev} {next}\n' +
+                '    </div>\n' +
+                '  </div>\n' +
+                '</div>\n';
+            tProgress = '<div class="progress">\n' +
+                '    <div class="{class}" role="progressbar"' +
+                ' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n' +
+                '        {status}\n' +
+                '     </div>\n' +
+                '</div>{stats}';
+            tStats = '<div class="text-info file-upload-stats">' +
+                '<span class="pending-time">{pendingTime}</span> ' +
+                '<span class="upload-speed">{uploadSpeed}</span>' +
+                '</div>';
+            tSize = ' <samp>({sizeText})</samp>';
+            tFooter = '<div class="file-thumbnail-footer">\n' +
+                '    <div class="file-footer-caption" title="{caption}">\n' +
+                '        <div class="file-caption-info">{caption}</div>\n' +
+                '        <div class="file-size-info">{size}</div>\n' +
+                '    </div>\n' +
+                '    {progress}\n{indicator}\n{actions}\n' +
+                '</div>';
+            tActions = '<div class="file-actions">\n' +
+                '    <div class="file-footer-buttons">\n' +
+                '        {download} {upload} {delete} {zoom} {other}' +
+                '    </div>\n' +
+                '</div>\n' +
+                '{drag}\n' +
+                '<div class="clearfix"></div>';
+            //noinspection HtmlUnknownAttribute
+            tActionDelete = '<button type="button" class="kv-file-remove {removeClass}" ' +
+                'title="{removeTitle}" {dataUrl}{dataKey}>{removeIcon}</button>\n';
+            tActionUpload = '<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">' +
+                '{uploadIcon}</button>';
+            tActionDownload = '<a class="kv-file-download {downloadClass}" title="{downloadTitle}" ' +
+                'href="{downloadUrl}" download="{caption}" target="_blank">{downloadIcon}</a>';
+            tActionZoom = '<button type="button" class="kv-file-zoom {zoomClass}" ' +
+                'title="{zoomTitle}">{zoomIcon}</button>';
+            tActionDrag = '<span class="file-drag-handle {dragClass}" title="{dragTitle}">{dragIcon}</span>';
+            tIndicator = '<div class="file-upload-indicator" title="{indicatorTitle}">{indicator}</div>';
+            tTagBef = '<div class="file-preview-frame {frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
+                ' data-fileid="{fileid}" data-template="{template}"';
+            tTagBef1 = tTagBef + '><div class="kv-file-content">\n';
+            tTagBef2 = tTagBef + ' title="{caption}"><div class="kv-file-content">\n';
+            tTagAft = '</div>{footer}\n</div>\n';
+            tGeneric = '{content}\n';
+            tStyle = ' {style}';
+            tHtml = '<div class="kv-preview-data file-preview-html" title="{caption}"' + tStyle + '>{data}</div>\n';
+            tImage = '<img src="{data}" class="file-preview-image kv-preview-data" title="{title}" ' +
+                'alt="{alt}"' + tStyle + '>\n';
+            tText = '<textarea class="kv-preview-data file-preview-text" title="{caption}" readonly' + tStyle + '>' +
+                '{data}</textarea>\n';
+            tOffice = '<iframe class="kv-preview-data file-preview-office" ' +
+                'src="https://view.officeapps.live.com/op/embed.aspx?src={data}"' + tStyle + '></iframe>';
+            tGdocs = '<iframe class="kv-preview-data file-preview-gdocs" ' +
+                'src="https://docs.google.com/gview?url={data}&embedded=true"' + tStyle + '></iframe>';
+            tVideo = '<video class="kv-preview-data file-preview-video" controls' + tStyle + '>\n' +
+                '<source src="{data}" type="{type}">\n' + $h.DEFAULT_PREVIEW + '\n</video>\n';
+            tAudio = '<!--suppress ALL --><audio class="kv-preview-data file-preview-audio" controls' + tStyle + '>\n<source src="{data}" ' +
+                'type="{type}">\n' + $h.DEFAULT_PREVIEW + '\n</audio>\n';
+            tFlash = '<embed class="kv-preview-data file-preview-flash" src="{data}" type="application/x-shockwave-flash"' + tStyle + '>\n';
+            tPdf = '<embed class="kv-preview-data file-preview-pdf" src="{data}" type="application/pdf"' + tStyle + '>\n';
+            tObject = '<object class="kv-preview-data file-preview-object file-object {typeCss}" ' +
+                'data="{data}" type="{type}"' + tStyle + '>\n' + '<param name="movie" value="{caption}" />\n' +
+                $h.OBJECT_PARAMS + ' ' + $h.DEFAULT_PREVIEW + '\n</object>\n';
+            tOther = '<div class="kv-preview-data file-preview-other-frame"' + tStyle + '>\n' + $h.DEFAULT_PREVIEW + '\n</div>\n';
+            tZoomCache = '<div class="kv-zoom-cache" style="display:none">{zoomContent}</div>';
+            vDefaultDim = {width: '100%', height: '100%', 'min-height': '480px'};
+            if (self._isPdfRendered()) {
+                tPdf = self.pdfRendererTemplate.replace('{renderer}', self._encodeURI(self.pdfRendererUrl));
+            }
+            self.defaults = {
+                layoutTemplates: {
+                    main1: tMain1,
+                    main2: tMain2,
+                    preview: tPreview,
+                    close: tClose,
+                    fileIcon: tFileIcon,
+                    caption: tCaption,
+                    modalMain: tModalMain,
+                    modal: tModal,
+                    progress: tProgress,
+                    stats: tStats,
+                    size: tSize,
+                    footer: tFooter,
+                    indicator: tIndicator,
+                    actions: tActions,
+                    actionDelete: tActionDelete,
+                    actionUpload: tActionUpload,
+                    actionDownload: tActionDownload,
+                    actionZoom: tActionZoom,
+                    actionDrag: tActionDrag,
+                    btnDefault: tBtnDefault,
+                    btnLink: tBtnLink,
+                    btnBrowse: tBtnBrowse,
+                    zoomCache: tZoomCache
+                },
+                previewMarkupTags: {
+                    tagBefore1: tTagBef1,
+                    tagBefore2: tTagBef2,
+                    tagAfter: tTagAft
+                },
+                previewContentTemplates: {
+                    generic: tGeneric,
+                    html: tHtml,
+                    image: tImage,
+                    text: tText,
+                    office: tOffice,
+                    gdocs: tGdocs,
+                    video: tVideo,
+                    audio: tAudio,
+                    flash: tFlash,
+                    object: tObject,
+                    pdf: tPdf,
+                    other: tOther
+                },
+                allowedPreviewTypes: ['image', 'html', 'text', 'video', 'audio', 'flash', 'pdf', 'object'],
+                previewTemplates: {},
+                previewSettings: {
+                    image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'},
+                    html: {width: '213px', height: '160px'},
+                    text: {width: '213px', height: '160px'},
+                    office: {width: '213px', height: '160px'},
+                    gdocs: {width: '213px', height: '160px'},
+                    video: {width: '213px', height: '160px'},
+                    audio: {width: '100%', height: '30px'},
+                    flash: {width: '213px', height: '160px'},
+                    object: {width: '213px', height: '160px'},
+                    pdf: {width: '100%', height: '160px'},
+                    other: {width: '213px', height: '160px'}
+                },
+                previewSettingsSmall: {
+                    image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'},
+                    html: {width: '100%', height: '160px'},
+                    text: {width: '100%', height: '160px'},
+                    office: {width: '100%', height: '160px'},
+                    gdocs: {width: '100%', height: '160px'},
+                    video: {width: '100%', height: 'auto'},
+                    audio: {width: '100%', height: '30px'},
+                    flash: {width: '100%', height: 'auto'},
+                    object: {width: '100%', height: 'auto'},
+                    pdf: {width: '100%', height: '160px'},
+                    other: {width: '100%', height: '160px'}
+                },
+                previewZoomSettings: {
+                    image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'},
+                    html: vDefaultDim,
+                    text: vDefaultDim,
+                    office: {width: '100%', height: '100%', 'max-width': '100%', 'min-height': '480px'},
+                    gdocs: {width: '100%', height: '100%', 'max-width': '100%', 'min-height': '480px'},
+                    video: {width: 'auto', height: '100%', 'max-width': '100%'},
+                    audio: {width: '100%', height: '30px'},
+                    flash: {width: 'auto', height: '480px'},
+                    object: {width: 'auto', height: '100%', 'max-width': '100%', 'min-height': '480px'},
+                    pdf: vDefaultDim,
+                    other: {width: 'auto', height: '100%', 'min-height': '480px'}
+                },
+                mimeTypeAliases: {
+                    'video/quicktime': 'video/mp4'
+                },
+                fileTypeSettings: {
+                    image: function (vType, vName) {
+                        return ($h.compare(vType, 'image.*') && !$h.compare(vType, /(tiff?|wmf)$/i) ||
+                            $h.compare(vName, /\.(gif|png|jpe?g)$/i));
+                    },
+                    html: function (vType, vName) {
+                        return $h.compare(vType, 'text/html') || $h.compare(vName, /\.(htm|html)$/i);
+                    },
+                    office: function (vType, vName) {
+                        return $h.compare(vType, /(word|excel|powerpoint|office)$/i) ||
+                            $h.compare(vName, /\.(docx?|xlsx?|pptx?|pps|potx?)$/i);
+                    },
+                    gdocs: function (vType, vName) {
+                        return $h.compare(vType, /(word|excel|powerpoint|office|iwork-pages|tiff?)$/i) ||
+                            $h.compare(vName,
+                                /\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i);
+                    },
+                    text: function (vType, vName) {
+                        return $h.compare(vType, 'text.*') || $h.compare(vName, /\.(xml|javascript)$/i) ||
+                            $h.compare(vName, /\.(txt|md|csv|nfo|ini|json|php|js|css)$/i);
+                    },
+                    video: function (vType, vName) {
+                        return $h.compare(vType, 'video.*') && ($h.compare(vType, /(ogg|mp4|mp?g|mov|webm|3gp)$/i) ||
+                            $h.compare(vName, /\.(og?|mp4|webm|mp?g|mov|3gp)$/i));
+                    },
+                    audio: function (vType, vName) {
+                        return $h.compare(vType, 'audio.*') && ($h.compare(vName, /(ogg|mp3|mp?g|wav)$/i) ||
+                            $h.compare(vName, /\.(og?|mp3|mp?g|wav)$/i));
+                    },
+                    flash: function (vType, vName) {
+                        return $h.compare(vType, 'application/x-shockwave-flash', true) || $h.compare(vName,
+                            /\.(swf)$/i);
+                    },
+                    pdf: function (vType, vName) {
+                        return $h.compare(vType, 'application/pdf', true) || $h.compare(vName, /\.(pdf)$/i);
+                    },
+                    object: function () {
+                        return true;
+                    },
+                    other: function () {
+                        return true;
+                    }
+                },
+                fileActionSettings: {
+                    showRemove: true,
+                    showUpload: true,
+                    showDownload: true,
+                    showZoom: true,
+                    showDrag: true,
+                    removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
+                    removeClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
+                    removeErrorClass: 'btn btn-sm btn-kv btn-danger',
+                    removeTitle: 'Remove file',
+                    uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
+                    uploadClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
+                    uploadTitle: 'Upload file',
+                    uploadRetryIcon: '<i class="glyphicon glyphicon-repeat"></i>',
+                    uploadRetryTitle: 'Retry upload',
+                    downloadIcon: '<i class="glyphicon glyphicon-download"></i>',
+                    downloadClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
+                    downloadTitle: 'Download file',
+                    zoomIcon: '<i class="glyphicon glyphicon-zoom-in"></i>',
+                    zoomClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
+                    zoomTitle: 'View Details',
+                    dragIcon: '<i class="glyphicon glyphicon-move"></i>',
+                    dragClass: 'text-info',
+                    dragTitle: 'Move / Rearrange',
+                    dragSettings: {},
+                    indicatorNew: '<i class="glyphicon glyphicon-plus-sign text-warning"></i>',
+                    indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign text-success"></i>',
+                    indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
+                    indicatorLoading: '<i class="glyphicon glyphicon-hourglass text-muted"></i>',
+                    indicatorPaused: '<i class="glyphicon glyphicon-pause text-primary"></i>',
+                    indicatorNewTitle: 'Not uploaded yet',
+                    indicatorSuccessTitle: 'Uploaded',
+                    indicatorErrorTitle: 'Upload Error',
+                    indicatorLoadingTitle: 'Uploading ...',
+                    indicatorPausedTitle: 'Upload Paused'
+                }
+            };
+            $.each(self.defaults, function (key, setting) {
+                if (key === 'allowedPreviewTypes') {
+                    if (self.allowedPreviewTypes === undefined) {
+                        self.allowedPreviewTypes = setting;
+                    }
+                    return;
+                }
+                self[key] = $.extend(true, {}, setting, self[key]);
+            });
+            self._initPreviewTemplates();
+        },
+        _initPreviewTemplates: function () {
+            var self = this, tags = self.previewMarkupTags, tagBef, tagAft = tags.tagAfter;
+            $.each(self.previewContentTemplates, function (key, value) {
+                if ($h.isEmpty(self.previewTemplates[key])) {
+                    tagBef = tags.tagBefore2;
+                    if (key === 'generic' || key === 'image' || key === 'html' || key === 'text') {
+                        tagBef = tags.tagBefore1;
+                    }
+                    if (self._isPdfRendered() && key === 'pdf') {
+                        tagBef = tagBef.replace('kv-file-content', 'kv-file-content kv-pdf-rendered');
+                    }
+                    self.previewTemplates[key] = tagBef + value + tagAft;
+                }
+            });
+        },
+        _initPreviewCache: function () {
+            var self = this;
+            self.previewCache = {
+                data: {},
+                init: function () {
+                    var content = self.initialPreview;
+                    if (content.length > 0 && !$h.isArray(content)) {
+                        content = content.split(self.initialPreviewDelimiter);
+                    }
+                    self.previewCache.data = {
+                        content: content,
+                        config: self.initialPreviewConfig,
+                        tags: self.initialPreviewThumbTags
+                    };
+                },
+                count: function (skipNull) {
+                    if (!self.previewCache.data || !self.previewCache.data.content) {
+                        return 0;
+                    }
+                    if (skipNull) {
+                        var chk = self.previewCache.data.content.filter(function (n) {
+                            return n !== null;
+                        });
+                        return chk.length;
+                    }
+                    return self.previewCache.data.content.length;
+                },
+                get: function (i, isDisabled) {
+                    var ind = 'init_' + i, data = self.previewCache.data, config = data.config[i], fileId,
+                        content = data.content[i], previewId = self.previewInitId + '-' + ind, out, $tmp, cat, ftr,
+                        fname, ftype, frameClass, asData = $h.ifSet('previewAsData', config, self.initialPreviewAsData),
+                        a = config ? {title: config.title || null, alt: config.alt || null} : {title: null, alt: null},
+                        parseTemplate = function (cat, dat, fn, ft, id, ftr, ind, fc, t) {
+                            fc = ' file-preview-initial ' + $h.SORT_CSS + (fc ? ' ' + fc : '');
+                            /** @namespace config.zoomData */
+                            fileId = config && config.fileId || 'file_' + id;
+                            return self._generatePreviewTemplate(cat, dat, fn, ft, id, fileId, false, null, fc,
+                                ftr, ind, t, a, config && config.zoomData || dat);
+                        };
+                    if (!content || !content.length) {
+                        return '';
+                    }
+                    isDisabled = isDisabled === undefined ? true : isDisabled;
+                    cat = $h.ifSet('type', config, self.initialPreviewFileType || 'generic');
+                    fname = $h.ifSet('filename', config, $h.ifSet('caption', config));
+                    ftype = $h.ifSet('filetype', config, cat);
+                    ftr = self.previewCache.footer(i, isDisabled, (config && config.size || null));
+                    frameClass = $h.ifSet('frameClass', config);
+                    if (asData) {
+                        out = parseTemplate(cat, content, fname, ftype, previewId, ftr, ind, frameClass);
+                    } else {
+                        out = parseTemplate('generic', content, fname, ftype, previewId, ftr, ind, frameClass, cat)
+                            .setTokens({'content': data.content[i]});
+                    }
+                    if (data.tags.length && data.tags[i]) {
+                        out = $h.replaceTags(out, data.tags[i]);
+                    }
+                    /** @namespace config.frameAttr */
+                    if (!$h.isEmpty(config) && !$h.isEmpty(config.frameAttr)) {
+                        $tmp = $(document.createElement('div')).html(out);
+                        $tmp.find('.file-preview-initial').attr(config.frameAttr);
+                        out = $tmp.html();
+                        $tmp.remove();
+                    }
+                    return out;
+                },
+                clean: function (data) {
+                    data.content = $h.cleanArray(data.content);
+                    data.config = $h.cleanArray(data.config);
+                    data.tags = $h.cleanArray(data.tags);
+                    self.previewCache.data = data;
+                },
+                add: function (content, config, tags, append) {
+                    var data = self.previewCache.data, index = content.length - 1;
+                    if (!content || !content.length) {
+                        return index;
+                    }
+                    if (!$h.isArray(content)) {
+                        content = content.split(self.initialPreviewDelimiter);
+                    }
+                    if (append) {
+                        index = data.content.push(content[0]) - 1;
+                        data.config[index] = config;
+                        data.tags[index] = tags;
+                    } else {
+                        data.content = content;
+                        data.config = config;
+                        data.tags = tags;
+                    }
+                    self.previewCache.clean(data);
+                    return index;
+                },
+                set: function (content, config, tags, append) {
+                    var data = self.previewCache.data, i, chk;
+                    if (!content || !content.length) {
+                        return;
+                    }
+                    if (!$h.isArray(content)) {
+                        content = content.split(self.initialPreviewDelimiter);
+                    }
+                    chk = content.filter(function (n) {
+                        return n !== null;
+                    });
+                    if (!chk.length) {
+                        return;
+                    }
+                    if (data.content === undefined) {
+                        data.content = [];
+                    }
+                    if (data.config === undefined) {
+                        data.config = [];
+                    }
+                    if (data.tags === undefined) {
+                        data.tags = [];
+                    }
+                    if (append) {
+                        for (i = 0; i < content.length; i++) {
+                            if (content[i]) {
+                                data.content.push(content[i]);
+                            }
+                        }
+                        for (i = 0; i < config.length; i++) {
+                            if (config[i]) {
+                                data.config.push(config[i]);
+                            }
+                        }
+                        for (i = 0; i < tags.length; i++) {
+                            if (tags[i]) {
+                                data.tags.push(tags[i]);
+                            }
+                        }
+                    } else {
+                        data.content = content;
+                        data.config = config;
+                        data.tags = tags;
+                    }
+                    self.previewCache.clean(data);
+                },
+                unset: function (index) {
+                    var chk = self.previewCache.count(), rev = self.reversePreviewOrder;
+                    if (!chk) {
+                        return;
+                    }
+                    if (chk === 1) {
+                        self.previewCache.data.content = [];
+                        self.previewCache.data.config = [];
+                        self.previewCache.data.tags = [];
+                        self.initialPreview = [];
+                        self.initialPreviewConfig = [];
+                        self.initialPreviewThumbTags = [];
+                        return;
+                    }
+                    self.previewCache.data.content = $h.spliceArray(self.previewCache.data.content, index, rev);
+                    self.previewCache.data.config = $h.spliceArray(self.previewCache.data.config, index, rev);
+                    self.previewCache.data.tags = $h.spliceArray(self.previewCache.data.tags, index, rev);
+                    var data = $.extend(true, {}, self.previewCache.data);
+                    self.previewCache.clean(data);
+                },
+                out: function () {
+                    var html = '', caption, len = self.previewCache.count(), i, content;
+                    if (len === 0) {
+                        return {content: '', caption: ''};
+                    }
+                    for (i = 0; i < len; i++) {
+                        content = self.previewCache.get(i);
+                        html = self.reversePreviewOrder ? (content + html) : (html + content);
+                    }
+                    caption = self._getMsgSelected(len);
+                    return {content: html, caption: caption};
+                },
+                footer: function (i, isDisabled, size) {
+                    var data = self.previewCache.data || {};
+                    if ($h.isEmpty(data.content)) {
+                        return '';
+                    }
+                    if ($h.isEmpty(data.config) || $h.isEmpty(data.config[i])) {
+                        data.config[i] = {};
+                    }
+                    isDisabled = isDisabled === undefined ? true : isDisabled;
+                    var config = data.config[i], caption = $h.ifSet('caption', config), a,
+                        width = $h.ifSet('width', config, 'auto'), url = $h.ifSet('url', config, false),
+                        key = $h.ifSet('key', config, null), fileId = $h.ifSet('fileId', config, null),
+                        fs = self.fileActionSettings, initPreviewShowDel = self.initialPreviewShowDelete || false,
+                        downloadInitialUrl = !self.initialPreviewDownloadUrl ? '' :
+                            self.initialPreviewDownloadUrl + '?key=' + key + (fileId ? '&fileId=' + fileId : ''),
+                        dUrl = config.downloadUrl || downloadInitialUrl,
+                        dFil = config.filename || config.caption || '',
+                        initPreviewShowDwl = !!(dUrl),
+                        sDel = $h.ifSet('showRemove', config, $h.ifSet('showRemove', fs, initPreviewShowDel)),
+                        sDwl = $h.ifSet('showDownload', config, $h.ifSet('showDownload', fs, initPreviewShowDwl)),
+                        sZm = $h.ifSet('showZoom', config, $h.ifSet('showZoom', fs, true)),
+                        sDrg = $h.ifSet('showDrag', config, $h.ifSet('showDrag', fs, true)),
+                        dis = (url === false) && isDisabled;
+                    sDwl = sDwl && config.downloadUrl !== false && !!dUrl;
+                    a = self._renderFileActions(config, false, sDwl, sDel, sZm, sDrg, dis, url, key, true, dUrl, dFil);
+                    return self._getLayoutTemplate('footer').setTokens({
+                        'progress': self._renderThumbProgress(),
+                        'actions': a,
+                        'caption': caption,
+                        'size': self._getSize(size),
+                        'width': width,
+                        'indicator': ''
+                    });
+                }
+            };
+            self.previewCache.init();
+        },
+        _isPdfRendered: function () {
+            var self = this, useLib = self.usePdfRenderer,
+                flag = typeof useLib === 'function' ? useLib() : !!useLib;
+            return flag && self.pdfRendererUrl;
+        },
+        _handler: function ($el, event, callback) {
+            var self = this, ns = self.namespace, ev = event.split(' ').join(ns + ' ') + ns;
+            if (!$el || !$el.length) {
+                return;
+            }
+            $el.off(ev).on(ev, callback);
+        },
+        _encodeURI: function (vUrl) {
+            var self = this;
+            return self.encodeUrl ? encodeURI(vUrl) : vUrl;
+        },
+        _log: function (msg, tokens) {
+            var self = this, id = self.$element.attr('id');
+            if (id) {
+                msg = '"' + id + '": ' + msg;
+            }
+            msg = 'bootstrap-fileinput: ' + msg;
+            if (typeof tokens === 'object') {
+                msg.setTokens(tokens);
+            }
+            if (typeof window.console.log !== 'undefined') {
+                window.console.log(msg);
+            } else {
+                window.alert(msg);
+            }
+        },
+        _validate: function () {
+            var self = this, status = self.$element.attr('type') === 'file';
+            if (!status) {
+                self._log($h.logMessages.badInputType);
+            }
+            return status;
+        },
+        _errorsExist: function () {
+            var self = this, $err, $errList = self.$errorContainer.find('li');
+            if ($errList.length) {
+                return true;
+            }
+            $err = $(document.createElement('div')).html(self.$errorContainer.html());
+            $err.find('.kv-error-close').remove();
+            $err.find('ul').remove();
+            return !!$.trim($err.text()).length;
+        },
+        _errorHandler: function (evt, caption) {
+            var self = this, err = evt.target.error, showError = function (msg) {
+                self._showError(msg.replace('{name}', caption));
+            };
+            /** @namespace err.NOT_FOUND_ERR */
+            /** @namespace err.SECURITY_ERR */
+            /** @namespace err.NOT_READABLE_ERR */
+            if (err.code === err.NOT_FOUND_ERR) {
+                showError(self.msgFileNotFound);
+            } else {
+                if (err.code === err.SECURITY_ERR) {
+                    showError(self.msgFileSecured);
+                } else {
+                    if (err.code === err.NOT_READABLE_ERR) {
+                        showError(self.msgFileNotReadable);
+                    } else {
+                        if (err.code === err.ABORT_ERR) {
+                            showError(self.msgFilePreviewAborted);
+                        } else {
+                            showError(self.msgFilePreviewError);
+                        }
+                    }
+                }
+            }
+        },
+        _addError: function (msg) {
+            var self = this, $error = self.$errorContainer;
+            if (msg && $error.length) {
+                $error.html(self.errorCloseButton + msg);
+                self._handler($error.find('.kv-error-close'), 'click', function () {
+                    setTimeout(function () {
+                        if (self.showPreview && !self.getFrames().length) {
+                            self.clear();
+                        }
+                        $error.fadeOut('slow');
+                    }, self.processDelay);
+                });
+            }
+        },
+        _setValidationError: function (css) {
+            var self = this;
+            css = (css ? css + ' ' : '') + 'has-error';
+            self.$container.removeClass(css).addClass('has-error');
+            $h.addCss(self.$captionContainer, 'is-invalid');
+        },
+        _resetErrors: function (fade) {
+            var self = this, $error = self.$errorContainer;
+            self.isError = false;
+            self.$container.removeClass('has-error');
+            self.$captionContainer.removeClass('is-invalid');
+            $error.html('');
+            if (fade) {
+                $error.fadeOut('slow');
+            } else {
+                $error.hide();
+            }
+        },
+        _showFolderError: function (folders) {
+            var self = this, $error = self.$errorContainer, msg;
+            if (!folders) {
+                return;
+            }
+            if (!self.isAjaxUpload) {
+                self._clearFileInput();
+            }
+            msg = self.msgFoldersNotAllowed.replace('{n}', folders);
+            self._addError(msg);
+            self._setValidationError();
+            $error.fadeIn(800);
+            self._raise('filefoldererror', [folders, msg]);
+        },
+        _showFileError: function (msg, params, event) {
+            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror',
+                fId = params && params.fileId || '', e = params && params.id ?
+                '<li data-thumb-id="' + params.id + '" data-file-id="' + fId + '">' + msg + '</li>' : '<li>' + msg + '</li>';
+            if ($error.find('ul').length === 0) {
+                self._addError('<ul>' + e + '</ul>');
+            } else {
+                $error.find('ul').append(e);
+            }
+            $error.fadeIn(800);
+            self._raise(ev, [params, msg]);
+            self._setValidationError('file-input-new');
+            return true;
+        },
+        _showError: function (msg, params, event) {
+            var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
+            params = params || {};
+            params.reader = self.reader;
+            self._addError(msg);
+            $error.fadeIn(800);
+            self._raise(ev, [params, msg]);
+            if (!self.isAjaxUpload) {
+                self._clearFileInput();
+            }
+            self._setValidationError('file-input-new');
+            self.$btnUpload.attr('disabled', true);
+            return true;
+        },
+        _noFilesError: function (params) {
+            var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
+                msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label),
+                $error = self.$errorContainer;
+            self._addError(msg);
+            self.isError = true;
+            self._updateFileDetails(0);
+            $error.fadeIn(800);
+            self._raise('fileerror', [params, msg]);
+            self._clearFileInput();
+            self._setValidationError();
+        },
+        _parseError: function (operation, jqXHR, errorThrown, fileName) {
+            /** @namespace jqXHR.responseJSON */
+            var self = this, errMsg = $.trim(errorThrown + ''), textPre,
+                text = jqXHR.responseJSON !== undefined && jqXHR.responseJSON.error !== undefined ?
+                    jqXHR.responseJSON.error : jqXHR.responseText;
+            if (self.cancelling && self.msgUploadAborted) {
+                errMsg = self.msgUploadAborted;
+            }
+            if (self.showAjaxErrorDetails && text) {
+                text = $.trim(text.replace(/\n\s*\n/g, '\n'));
+                textPre = text.length ? '<pre>' + text + '</pre>' : '';
+                errMsg += errMsg ? textPre : text;
+            }
+            if (!errMsg) {
+                errMsg = self.msgAjaxError.replace('{operation}', operation);
+            }
+            self.cancelling = false;
+            return fileName ? '<b>' + fileName + ': </b>' + errMsg : errMsg;
+        },
+        _parseFileType: function (type, name) {
+            var self = this, isValid, vType, cat, i, types = self.allowedPreviewTypes || [];
+            if (type === 'application/text-plain') {
+                return 'text';
+            }
+            for (i = 0; i < types.length; i++) {
+                cat = types[i];
+                isValid = self.fileTypeSettings[cat];
+                vType = isValid(type, name) ? cat : '';
+                if (!$h.isEmpty(vType)) {
+                    return vType;
+                }
+            }
+            return 'other';
+        },
+        _getPreviewIcon: function (fname) {
+            var self = this, ext, out = null;
+            if (fname && fname.indexOf('.') > -1) {
+                ext = fname.split('.').pop();
+                if (self.previewFileIconSettings) {
+                    out = self.previewFileIconSettings[ext] || self.previewFileIconSettings[ext.toLowerCase()] || null;
+                }
+                if (self.previewFileExtSettings) {
+                    $.each(self.previewFileExtSettings, function (key, func) {
+                        if (self.previewFileIconSettings[key] && func(ext)) {
+                            out = self.previewFileIconSettings[key];
+                            //noinspection UnnecessaryReturnStatementJS
+                            return;
+                        }
+                    });
+                }
+            }
+            return out;
+        },
+        _parseFilePreviewIcon: function (content, fname) {
+            var self = this, icn = self._getPreviewIcon(fname) || self.previewFileIcon, out = content;
+            if (out.indexOf('{previewFileIcon}') > -1) {
+                out = out.setTokens({'previewFileIconClass': self.previewFileIconClass, 'previewFileIcon': icn});
+            }
+            return out;
+        },
+        _raise: function (event, params) {
+            var self = this, e = $.Event(event);
+            if (params !== undefined) {
+                self.$element.trigger(e, params);
+            } else {
+                self.$element.trigger(e);
+            }
+            if (e.isDefaultPrevented() || e.result === false) {
+                return false;
+            }
+            switch (event) {
+                // ignore these events
+                case 'filebatchuploadcomplete':
+                case 'filebatchuploadsuccess':
+                case 'fileuploaded':
+                case 'fileclear':
+                case 'filecleared':
+                case 'filereset':
+                case 'fileerror':
+                case 'filefoldererror':
+                case 'fileuploaderror':
+                case 'filebatchuploaderror':
+                case 'filedeleteerror':
+                case 'filecustomerror':
+                case 'filesuccessremove':
+                    break;
+                // receive data response via `filecustomerror` event`
+                default:
+                    if (!self.ajaxAborted) {
+                        self.ajaxAborted = e.result;
+                    }
+                    break;
+            }
+            return true;
+        },
+        _listenFullScreen: function (isFullScreen) {
+            var self = this, $modal = self.$modal, $btnFull, $btnBord;
+            if (!$modal || !$modal.length) {
+                return;
+            }
+            $btnFull = $modal && $modal.find('.btn-fullscreen');
+            $btnBord = $modal && $modal.find('.btn-borderless');
+            if (!$btnFull.length || !$btnBord.length) {
+                return;
+            }
+            $btnFull.removeClass('active').attr('aria-pressed', 'false');
+            $btnBord.removeClass('active').attr('aria-pressed', 'false');
+            if (isFullScreen) {
+                $btnFull.addClass('active').attr('aria-pressed', 'true');
+            } else {
+                $btnBord.addClass('active').attr('aria-pressed', 'true');
+            }
+            if ($modal.hasClass('file-zoom-fullscreen')) {
+                self._maximizeZoomDialog();
+            } else {
+                if (isFullScreen) {
+                    self._maximizeZoomDialog();
+                } else {
+                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
+                }
+            }
+        },
+        _listen: function () {
+            var self = this, $el = self.$element, $form = self.$form, $cont = self.$container, fullScreenEvents;
+            self._handler($el, 'click', function (e) {
+                if ($el.hasClass('file-no-browse')) {
+                    if ($el.data('zoneClicked')) {
+                        $el.data('zoneClicked', false);
+                    } else {
+                        e.preventDefault();
+                    }
+                }
+            });
+            self._handler($el, 'change', $.proxy(self._change, self));
+            if (self.showBrowse) {
+                self._handler(self.$btnFile, 'click', $.proxy(self._browse, self));
+            }
+            self._handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self));
+            self._handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self));
+            self._handler($cont.find('.fileinput-pause'), 'click', $.proxy(self.pause, self));
+            self._initDragDrop();
+            self._handler($form, 'reset', $.proxy(self.clear, self));
+            if (!self.isAjaxUpload) {
+                self._handler($form, 'submit', $.proxy(self._submitForm, self));
+            }
+            self._handler(self.$container.find('.fileinput-upload'), 'click', $.proxy(self._uploadClick, self));
+            self._handler($(window), 'resize', function () {
+                self._listenFullScreen(screen.width === window.innerWidth && screen.height === window.innerHeight);
+            });
+            fullScreenEvents = 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange';
+            self._handler($(document), fullScreenEvents, function () {
+                self._listenFullScreen($h.checkFullScreen());
+            });
+            self._autoFitContent();
+            self._initClickable();
+            self._refreshPreview();
+        },
+        _autoFitContent: function () {
+            var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
+                self = this, config = width < 400 ? (self.previewSettingsSmall || self.defaults.previewSettingsSmall) :
+                (self.previewSettings || self.defaults.previewSettings), sel;
+            $.each(config, function (cat, settings) {
+                sel = '.file-preview-frame .file-preview-' + cat;
+                self.$preview.find(sel + '.kv-preview-data,' + sel + ' .kv-preview-data').css(settings);
+            });
+        },
+        _scanDroppedItems: function (item, files, path) {
+            path = path || '';
+            var self = this, i, dirReader, readDir, errorHandler = function (e) {
+                self._log($h.logMessages.badDroppedFiles);
+                self._log(e);
+            };
+            if (item.isFile) {
+                item.file(function (file) {
+                    files.push(file);
+                }, errorHandler);
+            } else {
+                if (item.isDirectory) {
+                    dirReader = item.createReader();
+                    readDir = function () {
+                        dirReader.readEntries(function (entries) {
+                            if (entries && entries.length > 0) {
+                                for (i = 0; i < entries.length; i++) {
+                                    self._scanDroppedItems(entries[i], files, path + item.name + '/');
+                                }
+                                // recursively call readDir() again, since browser can only handle first 100 entries.
+                                readDir();
+                            }
+                            return null;
+                        }, errorHandler);
+                    };
+                    readDir();
+                }
+            }
+
+        },
+        _initDragDrop: function () {
+            var self = this, $zone = self.$dropZone;
+            if (self.dropZoneEnabled && self.showPreview) {
+                self._handler($zone, 'dragenter dragover', $.proxy(self._zoneDragEnter, self));
+                self._handler($zone, 'dragleave', $.proxy(self._zoneDragLeave, self));
+                self._handler($zone, 'drop', $.proxy(self._zoneDrop, self));
+                self._handler($(document), 'dragenter dragover drop', self._zoneDragDropInit);
+            }
+        },
+        _zoneDragDropInit: function (e) {
+            e.stopPropagation();
+            e.preventDefault();
+        },
+        _zoneDragEnter: function (e) {
+            var self = this, dataTransfer = e.originalEvent.dataTransfer,
+                hasFiles = $.inArray('Files', dataTransfer.types) > -1;
+            self._zoneDragDropInit(e);
+            if (self.isDisabled || !hasFiles) {
+                e.originalEvent.dataTransfer.effectAllowed = 'none';
+                e.originalEvent.dataTransfer.dropEffect = 'none';
+                return;
+            }
+            if (self._raise('fileDragEnter', {'sourceEvent': e, 'files': dataTransfer.types.Files})) {
+                $h.addCss(self.$dropZone, 'file-highlighted');
+            }
+        },
+        _zoneDragLeave: function (e) {
+            var self = this;
+            self._zoneDragDropInit(e);
+            if (self.isDisabled) {
+                return;
+            }
+            if (self._raise('fileDragLeave', {'sourceEvent': e})) {
+                self.$dropZone.removeClass('file-highlighted');
+            }
+
+        },
+        _zoneDrop: function (e) {
+            /** @namespace e.originalEvent.dataTransfer */
+            var self = this, i, $el = self.$element, dataTransfer = e.originalEvent.dataTransfer,
+                files = dataTransfer.files, items = dataTransfer.items, folders = $h.getDragDropFolders(items),
+                processFiles = function () {
+                    if (!self.isAjaxUpload) {
+                        self.changeTriggered = true;
+                        $el.get(0).files = files;
+                        setTimeout(function () {
+                            self.changeTriggered = false;
+                            $el.trigger('change' + self.namespace);
+                        }, self.processDelay);
+                    } else {
+                        self._change(e, files);
+                    }
+                    self.$dropZone.removeClass('file-highlighted');
+                };
+            e.preventDefault();
+            if (self.isDisabled || $h.isEmpty(files)) {
+                return;
+            }
+            if (!self._raise('fileDragDrop', {'sourceEvent': e, 'files': files})) {
+                return;
+            }
+            if (folders > 0) {
+                if (!self.isAjaxUpload) {
+                    self._showFolderError(folders);
+                    return;
+                }
+                files = [];
+                for (i = 0; i < items.length; i++) {
+                    var item = items[i].webkitGetAsEntry();
+                    if (item) {
+                        self._scanDroppedItems(item, files);
+                    }
+                }
+                setTimeout(function () {
+                    processFiles();
+                }, 500);
+            } else {
+                processFiles();
+            }
+        },
+        _uploadClick: function (e) {
+            var self = this, $btn = self.$container.find('.fileinput-upload'), $form,
+                isEnabled = !$btn.hasClass('disabled') && $h.isEmpty($btn.attr('disabled'));
+            if (e && e.isDefaultPrevented()) {
+                return;
+            }
+            if (!self.isAjaxUpload) {
+                if (isEnabled && $btn.attr('type') !== 'submit') {
+                    $form = $btn.closest('form');
+                    // downgrade to normal form submit if possible
+                    if ($form.length) {
+                        $form.trigger('submit');
+                    }
+                    e.preventDefault();
+                }
+                return;
+            }
+            e.preventDefault();
+            if (isEnabled) {
+                self.upload();
+            }
+        },
+        _submitForm: function () {
+            var self = this;
+            return self._isFileSelectionValid() && !self._abort({});
+        },
+        _clearPreview: function () {
+            var self = this, $p = self.$preview,
+                $thumbs = self.showUploadedThumbs ? self.getFrames(':not(.file-preview-success)') : self.getFrames();
+            $thumbs.each(function () {
+                var $thumb = $(this);
+                $thumb.remove();
+                $h.cleanZoomCache($p.find('#zoom-' + $thumb.attr('id')));
+            });
+            if (!self.getFrames().length || !self.showPreview) {
+                self._resetUpload();
+            }
+            self._validateDefaultPreview();
+        },
+        _initSortable: function () {
+            var self = this, $el = self.$preview, settings, selector = '.' + $h.SORT_CSS,
+                rev = self.reversePreviewOrder;
+            if (!window.KvSortable || $el.find(selector).length === 0) {
+                return;
+            }
+            //noinspection JSUnusedGlobalSymbols
+            settings = {
+                handle: '.drag-handle-init',
+                dataIdAttr: 'data-preview-id',
+                scroll: false,
+                draggable: selector,
+                onSort: function (e) {
+                    var oldIndex = e.oldIndex, newIndex = e.newIndex, i = 0;
+                    self.initialPreview = $h.moveArray(self.initialPreview, oldIndex, newIndex, rev);
+                    self.initialPreviewConfig = $h.moveArray(self.initialPreviewConfig, oldIndex, newIndex, rev);
+                    self.previewCache.init();
+                    self.getFrames('.file-preview-initial').each(function () {
+                        $(this).attr('data-fileindex', 'init_' + i);
+                        i++;
+                    });
+                    self._raise('filesorted', {
+                        previewId: $(e.item).attr('id'),
+                        'oldIndex': oldIndex,
+                        'newIndex': newIndex,
+                        stack: self.initialPreviewConfig
+                    });
+                }
+            };
+            if ($el.data('kvsortable')) {
+                $el.kvsortable('destroy');
+            }
+            $.extend(true, settings, self.fileActionSettings.dragSettings);
+            $el.kvsortable(settings);
+        },
+        _setPreviewContent: function (content) {
+            var self = this;
+            self.$preview.html(content);
+            self._autoFitContent();
+        },
+        _initPreviewImageOrientations: function () {
+            var self = this, i = 0;
+            if (!self.autoOrientImageInitial) {
+                return;
+            }
+            self.getFrames('.file-preview-initial').each(function () {
+                var $thumb = $(this), $img, $zoomImg, id, config = self.initialPreviewConfig[i];
+                /** @namespace config.exif */
+                if (config && config.exif && config.exif.Orientation) {
+                    id = $thumb.attr('id');
+                    $img = $thumb.find('>.kv-file-content img');
+                    $zoomImg = self.$preview.find('#zoom-' + id + ' >.kv-file-content img');
+                    self.setImageOrientation($img, $zoomImg, config.exif.Orientation, $thumb);
+                }
+                i++;
+            });
+        },
+        _initPreview: function (isInit) {
+            var self = this, cap = self.initialCaption || '', out;
+            if (!self.previewCache.count(true)) {
+                self._clearPreview();
+                if (isInit) {
+                    self._setCaption(cap);
+                } else {
+                    self._initCaption();
+                }
+                return;
+            }
+            out = self.previewCache.out();
+            cap = isInit && self.initialCaption ? self.initialCaption : out.caption;
+            self._setPreviewContent(out.content);
+            self._setInitThumbAttr();
+            self._setCaption(cap);
+            self._initSortable();
+            if (!$h.isEmpty(out.content)) {
+                self.$container.removeClass('file-input-new');
+            }
+            self._initPreviewImageOrientations();
+        },
+        _getZoomButton: function (type) {
+            var self = this, label = self.previewZoomButtonIcons[type], css = self.previewZoomButtonClasses[type],
+                title = ' title="' + (self.previewZoomButtonTitles[type] || '') + '" ',
+                params = title + (type === 'close' ? ' data-dismiss="modal" aria-hidden="true"' : '');
+            if (type === 'fullscreen' || type === 'borderless' || type === 'toggleheader') {
+                params += ' data-toggle="button" aria-pressed="false" autocomplete="off"';
+            }
+            return '<button type="button" class="' + css + ' btn-' + type + '"' + params + '>' + label + '</button>';
+        },
+        _getModalContent: function () {
+            var self = this;
+            return self._getLayoutTemplate('modal').setTokens({
+                'rtl': self.rtl ? ' kv-rtl' : '',
+                'zoomFrameClass': self.frameClass,
+                'heading': self.msgZoomModalHeading,
+                'prev': self._getZoomButton('prev'),
+                'next': self._getZoomButton('next'),
+                'toggleheader': self._getZoomButton('toggleheader'),
+                'fullscreen': self._getZoomButton('fullscreen'),
+                'borderless': self._getZoomButton('borderless'),
+                'close': self._getZoomButton('close')
+            });
+        },
+        _listenModalEvent: function (event) {
+            var self = this, $modal = self.$modal, getParams = function (e) {
+                return {
+                    sourceEvent: e,
+                    previewId: $modal.data('previewId'),
+                    modal: $modal
+                };
+            };
+            $modal.on(event + '.bs.modal', function (e) {
+                var $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless');
+                self._raise('filezoom' + event, getParams(e));
+                if (event === 'shown') {
+                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
+                    $btnFull.removeClass('active').attr('aria-pressed', 'false');
+                    if ($modal.hasClass('file-zoom-fullscreen')) {
+                        self._maximizeZoomDialog();
+                        if ($h.checkFullScreen()) {
+                            $btnFull.addClass('active').attr('aria-pressed', 'true');
+                        } else {
+                            $btnBord.addClass('active').attr('aria-pressed', 'true');
+                        }
+                    }
+                }
+            });
+        },
+        _initZoom: function () {
+            var self = this, $dialog, modalMain = self._getLayoutTemplate('modalMain'), modalId = '#' + $h.MODAL_ID;
+            if (!self.showPreview) {
+                return;
+            }
+            self.$modal = $(modalId);
+            if (!self.$modal || !self.$modal.length) {
+                $dialog = $(document.createElement('div')).html(modalMain).insertAfter(self.$container);
+                self.$modal = $(modalId).insertBefore($dialog);
+                $dialog.remove();
+            }
+            $h.initModal(self.$modal);
+            self.$modal.html(self._getModalContent());
+            $.each($h.MODAL_EVENTS, function (key, event) {
+                self._listenModalEvent(event);
+            });
+        },
+        _initZoomButtons: function () {
+            var self = this, previewId = self.$modal.data('previewId') || '', $first, $last,
+                thumbs = self.getFrames().toArray(), len = thumbs.length, $prev = self.$modal.find('.btn-prev'),
+                $next = self.$modal.find('.btn-next');
+            if (thumbs.length < 2) {
+                $prev.hide();
+                $next.hide();
+                return;
+            } else {
+                $prev.show();
+                $next.show();
+            }
+            if (!len) {
+                return;
+            }
+            $first = $(thumbs[0]);
+            $last = $(thumbs[len - 1]);
+            $prev.removeAttr('disabled');
+            $next.removeAttr('disabled');
+            if ($first.length && $first.attr('id') === previewId) {
+                $prev.attr('disabled', true);
+            }
+            if ($last.length && $last.attr('id') === previewId) {
+                $next.attr('disabled', true);
+            }
+        },
+        _maximizeZoomDialog: function () {
+            var self = this, $modal = self.$modal, $head = $modal.find('.modal-header:visible'),
+                $foot = $modal.find('.modal-footer:visible'), $body = $modal.find('.modal-body'),
+                h = $(window).height(), diff = 0;
+            $modal.addClass('file-zoom-fullscreen');
+            if ($head && $head.length) {
+                h -= $head.outerHeight(true);
+            }
+            if ($foot && $foot.length) {
+                h -= $foot.outerHeight(true);
+            }
+            if ($body && $body.length) {
+                diff = $body.outerHeight(true) - $body.height();
+                h -= diff;
+            }
+            $modal.find('.kv-zoom-body').height(h);
+        },
+        _resizeZoomDialog: function (fullScreen) {
+            var self = this, $modal = self.$modal, $btnFull = $modal.find('.btn-fullscreen'),
+                $btnBord = $modal.find('.btn-borderless');
+            if ($modal.hasClass('file-zoom-fullscreen')) {
+                $h.toggleFullScreen(false);
+                if (!fullScreen) {
+                    if (!$btnFull.hasClass('active')) {
+                        $modal.removeClass('file-zoom-fullscreen');
+                        self.$modal.find('.kv-zoom-body').css('height', self.zoomModalHeight);
+                    } else {
+                        $btnFull.removeClass('active').attr('aria-pressed', 'false');
+                    }
+                } else {
+                    if (!$btnFull.hasClass('active')) {
+                        $modal.removeClass('file-zoom-fullscreen');
+                        self._resizeZoomDialog(true);
+                        if ($btnBord.hasClass('active')) {
+                            $btnBord.removeClass('active').attr('aria-pressed', 'false');
+                        }
+                    }
+                }
+            } else {
+                if (!fullScreen) {
+                    self._maximizeZoomDialog();
+                    return;
+                }
+                $h.toggleFullScreen(true);
+            }
+            $modal.focus();
+        },
+        _setZoomContent: function ($frame, animate) {
+            var self = this, $content, tmplt, body, title, $body, $dataEl, config, previewId = $frame.attr('id'),
+                $zoomPreview = self.$preview.find('#zoom-' + previewId), $modal = self.$modal, $tmp,
+                $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless'), cap, size,
+                $btnTogh = $modal.find('.btn-toggleheader');
+            tmplt = $zoomPreview.attr('data-template') || 'generic';
+            $content = $zoomPreview.find('.kv-file-content');
+            body = $content.length ? $content.html() : '';
+            cap = $frame.data('caption') || '';
+            size = $frame.data('size') || '';
+            title = cap + ' ' + size;
+            $modal.find('.kv-zoom-title').attr('title', $('<div/>').html(title).text()).html(title);
+            $body = $modal.find('.kv-zoom-body');
+            $modal.removeClass('kv-single-content');
+            if (animate) {
+                $tmp = $body.addClass('file-thumb-loading').clone().insertAfter($body);
+                $body.html(body).hide();
+                $tmp.fadeOut('fast', function () {
+                    $body.fadeIn('fast', function () {
+                        $body.removeClass('file-thumb-loading');
+                    });
+                    $tmp.remove();
+                });
+            } else {
+                $body.html(body);
+            }
+            config = self.previewZoomSettings[tmplt];
+            if (config) {
+                $dataEl = $body.find('.kv-preview-data');
+                $h.addCss($dataEl, 'file-zoom-detail');
+                $.each(config, function (key, value) {
+                    $dataEl.css(key, value);
+                    if (($dataEl.attr('width') && key === 'width') || ($dataEl.attr('height') && key === 'height')) {
+                        $dataEl.removeAttr(key);
+                    }
+                });
+            }
+            $modal.data('previewId', previewId);
+            self._handler($modal.find('.btn-prev'), 'click', function () {
+                self._zoomSlideShow('prev', previewId);
+            });
+            self._handler($modal.find('.btn-next'), 'click', function () {
+                self._zoomSlideShow('next', previewId);
+            });
+            self._handler($btnFull, 'click', function () {
+                self._resizeZoomDialog(true);
+            });
+            self._handler($btnBord, 'click', function () {
+                self._resizeZoomDialog(false);
+            });
+            self._handler($btnTogh, 'click', function () {
+                var $header = $modal.find('.modal-header'), $floatBar = $modal.find('.modal-body .floating-buttons'),
+                    ht, $actions = $header.find('.kv-zoom-actions'), resize = function (height) {
+                        var $body = self.$modal.find('.kv-zoom-body'), h = self.zoomModalHeight;
+                        if ($modal.hasClass('file-zoom-fullscreen')) {
+                            h = $body.outerHeight(true);
+                            if (!height) {
+                                h = h - $header.outerHeight(true);
+                            }
+                        }
+                        $body.css('height', height ? h + height : h);
+                    };
+                if ($header.is(':visible')) {
+                    ht = $header.outerHeight(true);
+                    $header.slideUp('slow', function () {
+                        $actions.find('.btn').appendTo($floatBar);
+                        resize(ht);
+                    });
+                } else {
+                    $floatBar.find('.btn').appendTo($actions);
+                    $header.slideDown('slow', function () {
+                        resize();
+                    });
+                }
+                $modal.focus();
+            });
+            self._handler($modal, 'keydown', function (e) {
+                var key = e.which || e.keyCode, $prev = $(this).find('.btn-prev'), $next = $(this).find('.btn-next'),
+                    vId = $(this).data('previewId'), vPrevKey = self.rtl ? 39 : 37, vNextKey = self.rtl ? 37 : 39;
+                if (key === vPrevKey && $prev.length && !$prev.attr('disabled')) {
+                    self._zoomSlideShow('prev', vId);
+                }
+                if (key === vNextKey && $next.length && !$next.attr('disabled')) {
+                    self._zoomSlideShow('next', vId);
+                }
+            });
+        },
+        _zoomPreview: function ($btn) {
+            var self = this, $frame, $modal = self.$modal;
+            if (!$btn.length) {
+                throw 'Cannot zoom to detailed preview!';
+            }
+            $h.initModal($modal);
+            $modal.html(self._getModalContent());
+            $frame = $btn.closest($h.FRAMES);
+            self._setZoomContent($frame);
+            $modal.modal('show');
+            self._initZoomButtons();
+        },
+        _zoomSlideShow: function (dir, previewId) {
+            var self = this, $btn = self.$modal.find('.kv-zoom-actions .btn-' + dir), $targFrame, i,
+                thumbs = self.getFrames().toArray(), len = thumbs.length, out;
+            if ($btn.attr('disabled')) {
+                return;
+            }
+            for (i = 0; i < len; i++) {
+                if ($(thumbs[i]).attr('id') === previewId) {
+                    out = dir === 'prev' ? i - 1 : i + 1;
+                    break;
+                }
+            }
+            if (out < 0 || out >= len || !thumbs[out]) {
+                return;
+            }
+            $targFrame = $(thumbs[out]);
+            if ($targFrame.length) {
+                self._setZoomContent($targFrame, true);
+            }
+            self._initZoomButtons();
+            self._raise('filezoom' + dir, {'previewId': previewId, modal: self.$modal});
+        },
+        _initZoomButton: function () {
+            var self = this;
+            self.$preview.find('.kv-file-zoom').each(function () {
+                var $el = $(this);
+                self._handler($el, 'click', function () {
+                    self._zoomPreview($el);
+                });
+            });
+        },
+        _inputFileCount: function () {
+            return this.$element.get(0).files.length;
+        },
+        _refreshPreview: function () {
+            var self = this, files;
+            if ((!self._inputFileCount() && !self.isAjaxUpload) || !self.showPreview || !self.isPreviewable) {
+                return;
+            }
+            if (self.isAjaxUpload) {
+                if (self.fileManager.count() > 0) {
+                    files = $.extend(true, {}, self.fileManager.stack);
+                    self.fileManager.clear();
+                    self._clearFileInput();
+                } else {
+                    files = self.$element.get(0).files;
+                }
+            } else {
+                files = self.$element.get(0).files;
+            }
+            if (files && files.length) {
+                self.readFiles(files);
+                self._setFileDropZoneTitle();
+            }
+        },
+        _clearObjects: function ($el) {
+            $el.find('video audio').each(function () {
+                this.pause();
+                $(this).remove();
+            });
+            $el.find('img object div').each(function () {
+                $(this).remove();
+            });
+        },
+        _clearFileInput: function () {
+            var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl;
+            if (!self._inputFileCount()) {
+                return;
+            }
+            $srcFrm = $el.closest('form');
+            $tmpFrm = $(document.createElement('form'));
+            $tmpEl = $(document.createElement('div'));
+            $el.before($tmpEl);
+            if ($srcFrm.length) {
+                $srcFrm.after($tmpFrm);
+            } else {
+                $tmpEl.after($tmpFrm);
+            }
+            $tmpFrm.append($el).trigger('reset');
+            $tmpEl.before($el).remove();
+            $tmpFrm.remove();
+        },
+        _resetUpload: function () {
+            var self = this;
+            self.uploadCache = {content: [], config: [], tags: [], append: true};
+            self.$btnUpload.removeAttr('disabled');
+            self._setProgress(0);
+            self.$progress.hide();
+            self._resetErrors(false);
+            self._initAjax();
+            self.fileManager.clearImages();
+            self._resetCanvas();
+            self.cacheInitialPreview = {};
+            if (self.overwriteInitial) {
+                self.initialPreview = [];
+                self.initialPreviewConfig = [];
+                self.initialPreviewThumbTags = [];
+                self.previewCache.data = {
+                    content: [],
+                    config: [],
+                    tags: []
+                };
+            }
+        },
+        _resetCanvas: function () {
+            var self = this;
+            if (self.canvas && self.imageCanvasContext) {
+                self.imageCanvasContext.clearRect(0, 0, self.canvas.width, self.canvas.height);
+            }
+        },
+        _hasInitialPreview: function () {
+            var self = this;
+            return !self.overwriteInitial && self.previewCache.count(true);
+        },
+        _resetPreview: function () {
+            var self = this, out, cap;
+            if (self.previewCache.count(true)) {
+                out = self.previewCache.out();
+                self._setPreviewContent(out.content);
+                self._setInitThumbAttr();
+                cap = self.initialCaption ? self.initialCaption : out.caption;
+                self._setCaption(cap);
+            } else {
+                self._clearPreview();
+                self._initCaption();
+            }
+            if (self.showPreview) {
+                self._initZoom();
+                self._initSortable();
+            }
+        },
+        _clearDefaultPreview: function () {
+            var self = this;
+            self.$preview.find('.file-default-preview').remove();
+        },
+        _validateDefaultPreview: function () {
+            var self = this;
+            if (!self.showPreview || $h.isEmpty(self.defaultPreviewContent)) {
+                return;
+            }
+            self._setPreviewContent('<div class="file-default-preview">' + self.defaultPreviewContent + '</div>');
+            self.$container.removeClass('file-input-new');
+            self._initClickable();
+        },
+        _resetPreviewThumbs: function (isAjax) {
+            var self = this, out;
+            if (isAjax) {
+                self._clearPreview();
+                self.clearFileStack();
+                return;
+            }
+            if (self._hasInitialPreview()) {
+                out = self.previewCache.out();
+                self._setPreviewContent(out.content);
+                self._setInitThumbAttr();
+                self._setCaption(out.caption);
+                self._initPreviewActions();
+            } else {
+                self._clearPreview();
+            }
+        },
+        _getLayoutTemplate: function (t) {
+            var self = this, template = self.layoutTemplates[t];
+            if ($h.isEmpty(self.customLayoutTags)) {
+                return template;
+            }
+            return $h.replaceTags(template, self.customLayoutTags);
+        },
+        _getPreviewTemplate: function (t) {
+            var self = this, template = self.previewTemplates[t];
+            if ($h.isEmpty(self.customPreviewTags)) {
+                return template;
+            }
+            return $h.replaceTags(template, self.customPreviewTags);
+        },
+        _getOutData: function (formdata, jqXHR, responseData, filesData) {
+            var self = this;
+            jqXHR = jqXHR || {};
+            responseData = responseData || {};
+            filesData = filesData || self.fileManager.list();
+            return {
+                formdata: formdata,
+                files: filesData,
+                filenames: self.filenames,
+                filescount: self.getFilesCount(),
+                extra: self._getExtraData(),
+                response: responseData,
+                reader: self.reader,
+                jqXHR: jqXHR
+            };
+        },
+        _getMsgSelected: function (n) {
+            var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural;
+            return n > 0 ? self.msgSelected.replace('{n}', n).replace('{files}', strFiles) : self.msgNoFilesSelected;
+        },
+        _getFrame: function (id) {
+            var self = this, $frame = $('#' + id);
+            if (!$frame.length) {
+                self._log($h.logMessages.invalidThumb, {id: id});
+                return null;
+            }
+            return $frame;
+        },
+        _getThumbs: function (css) {
+            css = css || '';
+            return this.getFrames(':not(.file-preview-initial)' + css);
+        },
+        _getExtraData: function (fileId, index) {
+            var self = this, data = self.uploadExtraData;
+            if (typeof self.uploadExtraData === 'function') {
+                data = self.uploadExtraData(fileId, index);
+            }
+            return data;
+        },
+        _initXhr: function (xhrobj, fileId, fileCount) {
+            var self = this, fm = self.fileManager, func = function (event) {
+                var pct = 0, total = event.total, loaded = event.loaded || event.position,
+                    stats = fm.getUploadStats(fileId, loaded, total);
+                /** @namespace event.lengthComputable */
+                if (event.lengthComputable && !self.enableResumableUpload) {
+                    pct = $h.round(loaded / total * 100);
+                }
+                if (fileId) {
+                    self._setFileUploadStats(fileId, pct, fileCount, stats);
+                } else {
+                    self._setProgress(pct, null, null, self._getStats(stats));
+                }
+                self._raise('fileajaxprogress', [stats]);
+            };
+            if (xhrobj.upload) {
+                if (self.progressDelay) {
+                    func = $h.debounce(func, self.progressDelay);
+                }
+                xhrobj.upload.addEventListener('progress', func, false);
+            }
+            return xhrobj;
+        },
+        _initAjaxSettings: function () {
+            var self = this;
+            self._ajaxSettings = $.extend(true, {}, self.ajaxSettings);
+            self._ajaxDeleteSettings = $.extend(true, {}, self.ajaxDeleteSettings);
+        },
+        _mergeAjaxCallback: function (funcName, srcFunc, type) {
+            var self = this, settings = self._ajaxSettings, flag = self.mergeAjaxCallbacks, targFunc;
+            if (type === 'delete') {
+                settings = self._ajaxDeleteSettings;
+                flag = self.mergeAjaxDeleteCallbacks;
+            }
+            targFunc = settings[funcName];
+            if (flag && typeof targFunc === 'function') {
+                if (flag === 'before') {
+                    settings[funcName] = function () {
+                        targFunc.apply(this, arguments);
+                        srcFunc.apply(this, arguments);
+                    };
+                } else {
+                    settings[funcName] = function () {
+                        srcFunc.apply(this, arguments);
+                        targFunc.apply(this, arguments);
+                    };
+                }
+            } else {
+                settings[funcName] = srcFunc;
+            }
+        },
+        _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, formdata, fileId, index, vUrl) {
+            var self = this, settings, defaults, data, processQueue;
+            if (!self._raise('filepreajax', [formdata, fileId, index])) {
+                return;
+            }
+            formdata.append('initialPreview', JSON.stringify(self.initialPreview));
+            formdata.append('initialPreviewConfig', JSON.stringify(self.initialPreviewConfig));
+            formdata.append('initialPreviewThumbTags', JSON.stringify(self.initialPreviewThumbTags));
+            self._initAjaxSettings();
+            self._mergeAjaxCallback('beforeSend', fnBefore);
+            self._mergeAjaxCallback('success', fnSuccess);
+            self._mergeAjaxCallback('complete', fnComplete);
+            self._mergeAjaxCallback('error', fnError);
+            vUrl = vUrl || self.uploadUrlThumb || self.uploadUrl;
+            if (typeof vUrl === 'function') {
+                vUrl = vUrl();
+            }
+            data = self._getExtraData(fileId, index) || {};
+            if (typeof data === 'object') {
+                $.each(data, function (key, value) {
+                    formdata.append(key, value);
+                });
+            }
+            defaults = {
+                xhr: function () {
+                    var xhrobj = $.ajaxSettings.xhr();
+                    return self._initXhr(xhrobj, fileId, self.fileManager.count());
+                },
+                url: self._encodeURI(vUrl),
+                type: 'POST',
+                dataType: 'json',
+                data: formdata,
+                cache: false,
+                processData: false,
+                contentType: false
+            };
+            settings = $.extend(true, {}, defaults, self._ajaxSettings);
+            self.ajaxQueue.push(settings);
+            processQueue = function () {
+                var config, xhr;
+                if (self.ajaxCurrentThreads < self.maxAjaxThreads) {
+                    config = self.ajaxQueue.shift();
+                    if (typeof config !== 'undefined') {
+                        self.ajaxCurrentThreads++;
+                        xhr = $.ajax(config).done(function () {
+                            clearInterval(self.ajaxQueueIntervalId);
+                            self.ajaxCurrentThreads--;
+                        });
+                        self.ajaxRequests.push(xhr);
+                    }
+                }
+            };
+            self.ajaxQueueIntervalId = setInterval(processQueue, self.queueDelay);
+
+        },
+        _mergeArray: function (prop, content) {
+            var self = this, arr1 = $h.cleanArray(self[prop]), arr2 = $h.cleanArray(content);
+            self[prop] = arr1.concat(arr2);
+        },
+        _initUploadSuccess: function (out, $thumb, allFiles) {
+            var self = this, append, data, index, $div, $newCache, content, config, tags, i;
+            if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) {
+                return;
+            }
+            if (out.initialPreview !== undefined && out.initialPreview.length > 0) {
+                self.hasInitData = true;
+                content = out.initialPreview || [];
+                config = out.initialPreviewConfig || [];
+                tags = out.initialPreviewThumbTags || [];
+                append = out.append === undefined || out.append;
+                if (content.length > 0 && !$h.isArray(content)) {
+                    content = content.split(self.initialPreviewDelimiter);
+                }
+                if (content.length) {
+                    self._mergeArray('initialPreview', content);
+                    self._mergeArray('initialPreviewConfig', config);
+                    self._mergeArray('initialPreviewThumbTags', tags);
+                }
+                if ($thumb !== undefined) {
+                    if (!allFiles) {
+                        index = self.previewCache.add(content[0], config[0], tags[0], append);
+                        data = self.previewCache.get(index, false);
+                        $div = $(document.createElement('div')).html(data).hide().insertAfter($thumb);
+                        $newCache = $div.find('.kv-zoom-cache');
+                        if ($newCache && $newCache.length) {
+                            $newCache.insertAfter($thumb);
+                        }
+                        $thumb.fadeOut('slow', function () {
+                            var $newThumb = $div.find('.file-preview-frame');
+                            if ($newThumb && $newThumb.length) {
+                                $newThumb.insertBefore($thumb).fadeIn('slow').css('display:inline-block');
+                            }
+                            self._initPreviewActions();
+                            self._clearFileInput();
+                            $h.cleanZoomCache(self.$preview.find('#zoom-' + $thumb.attr('id')));
+                            $thumb.remove();
+                            $div.remove();
+                            self._initSortable();
+                        });
+                    } else {
+                        i = $thumb.attr('data-fileindex');
+                        self.uploadCache.content[i] = content[0];
+                        self.uploadCache.config[i] = config[0] || [];
+                        self.uploadCache.tags[i] = tags[0] || [];
+                        self.uploadCache.append = append;
+                    }
+                } else {
+                    self.previewCache.set(content, config, tags, append);
+                    self._initPreview();
+                    self._initPreviewActions();
+                }
+            }
+        },
+        _initSuccessThumbs: function () {
+            var self = this;
+            if (!self.showPreview) {
+                return;
+            }
+            self._getThumbs($h.FRAMES + '.file-preview-success').each(function () {
+                var $thumb = $(this), $preview = self.$preview, $remove = $thumb.find('.kv-file-remove');
+                $remove.removeAttr('disabled');
+                self._handler($remove, 'click', function () {
+                    var id = $thumb.attr('id'),
+                        out = self._raise('filesuccessremove', [id, $thumb.attr('data-fileindex')]);
+                    $h.cleanMemory($thumb);
+                    if (out === false) {
+                        return;
+                    }
+                    $thumb.fadeOut('slow', function () {
+                        $h.cleanZoomCache($preview.find('#zoom-' + id));
+                        $thumb.remove();
+                        if (!self.getFrames().length) {
+                            self.reset();
+                        }
+                    });
+                });
+            });
+        },
+        _updateInitialPreview: function () {
+            var self = this, u = self.uploadCache, i, j, len = 0, data = self.cacheInitialPreview;
+            if (data && data.content) {
+                len = data.content.length;
+            }
+            if (self.showPreview) {
+                self.previewCache.set(u.content, u.config, u.tags, u.append);
+                if (len) {
+                    for (i = 0; i < u.content.length; i++) {
+                        j = i + len;
+                        data.content[j] = u.content[i];
+                        //noinspection JSUnresolvedVariable
+                        if (data.config.length) {
+                            data.config[j] = u.config[i];
+                        }
+                        if (data.tags.length) {
+                            data.tags[j] = u.tags[i];
+                        }
+                    }
+                    self.initialPreview = $h.cleanArray(data.content);
+                    self.initialPreviewConfig = $h.cleanArray(data.config);
+                    self.initialPreviewThumbTags = $h.cleanArray(data.tags);
+                } else {
+                    self.initialPreview = u.content;
+                    self.initialPreviewConfig = u.config;
+                    self.initialPreviewThumbTags = u.tags;
+                }
+                self.cacheInitialPreview = {};
+                if (self.hasInitData) {
+                    self._initPreview();
+                    self._initPreviewActions();
+                }
+            }
+        },
+        _uploadSingle: function (i, id, isBatch) {
+            var self = this, fm = self.fileManager, count = fm.count(), formdata = new FormData(), outData,
+                previewId = self.previewInitId + '-' + i, $thumb, chkComplete, $btnUpload, $btnDelete,
+                hasPostData = count > 0 || !$.isEmptyObject(self.uploadExtraData), uploadFailed, $prog, fnBefore,
+                errMsg, fnSuccess, fnComplete, fnError, updateUploadLog, op = self.ajaxOperations.uploadThumb,
+                fileObj = fm.getFile(id), params = {id: previewId, index: i, fileId: id},
+                fileName = self.fileManager.getFileName(id, true);
+            if (self.enableResumableUpload) { // not enabled for resumable uploads
+                return;
+            }
+            if (self.showPreview) {
+                $thumb = self.fileManager.getThumb(id);
+                $prog = $thumb.find('.file-thumb-progress');
+                $btnUpload = $thumb.find('.kv-file-upload');
+                $btnDelete = $thumb.find('.kv-file-remove');
+                $prog.show();
+            }
+            if (count === 0 || !hasPostData || (self.showPreview && $btnUpload && $btnUpload.hasClass('disabled')) ||
+                self._abort(params)) {
+                return;
+            }
+            updateUploadLog = function () {
+                if (!uploadFailed) {
+                    fm.removeFile(id);
+                } else {
+                    fm.errors.push(id);
+                }
+                fm.setProcessed(id);
+                if (fm.isProcessed()) {
+                    self.fileBatchCompleted = true;
+                }
+            };
+            chkComplete = function () {
+                var $initThumbs;
+                if (!self.fileBatchCompleted) {
+                    return;
+                }
+                setTimeout(function () {
+                    var triggerReset = fm.count() === 0, errCount = fm.errors.length;
+                    self._updateInitialPreview();
+                    self.unlock(triggerReset);
+                    if (triggerReset) {
+                        self._clearFileInput();
+                    }
+                    $initThumbs = self.$preview.find('.file-preview-initial');
+                    if (self.uploadAsync && $initThumbs.length) {
+                        $h.addCss($initThumbs, $h.SORT_CSS);
+                        self._initSortable();
+                    }
+                    self._raise('filebatchuploadcomplete', [fm.stack, self._getExtraData()]);
+                    if (!self.retryErrorUploads || errCount === 0) {
+                        fm.clear();
+                    }
+                    self._setProgress(101);
+                    self.ajaxAborted = false;
+                }, self.processDelay);
+            };
+            fnBefore = function (jqXHR) {
+                outData = self._getOutData(formdata, jqXHR);
+                fm.initStats(id);
+                self.fileBatchCompleted = false;
+                if (!isBatch) {
+                    self.ajaxAborted = false;
+                }
+                if (self.showPreview) {
+                    if (!$thumb.hasClass('file-preview-success')) {
+                        self._setThumbStatus($thumb, 'Loading');
+                        $h.addCss($thumb, 'file-uploading');
+                    }
+                    $btnUpload.attr('disabled', true);
+                    $btnDelete.attr('disabled', true);
+                }
+                if (!isBatch) {
+                    self.lock();
+                }
+                if (fm.errors.indexOf(id) !== -1) {
+                    delete fm.errors[id];
+                }
+                self._raise('filepreupload', [outData, previewId, i]);
+                $.extend(true, params, outData);
+                if (self._abort(params)) {
+                    jqXHR.abort();
+                    if (!isBatch) {
+                        self._setThumbStatus($thumb, 'New');
+                        $thumb.removeClass('file-uploading');
+                        $btnUpload.removeAttr('disabled');
+                        $btnDelete.removeAttr('disabled');
+                        self.unlock();
+                    }
+                    self._setProgressCancelled();
+                }
+            };
+            fnSuccess = function (data, textStatus, jqXHR) {
+                var pid = self.showPreview && $thumb.attr('id') ? $thumb.attr('id') : previewId;
+                outData = self._getOutData(formdata, jqXHR, data);
+                $.extend(true, params, outData);
+                setTimeout(function () {
+                    if ($h.isEmpty(data) || $h.isEmpty(data.error)) {
+                        if (self.showPreview) {
+                            self._setThumbStatus($thumb, 'Success');
+                            $btnUpload.hide();
+                            self._initUploadSuccess(data, $thumb, isBatch);
+                            self._setProgress(101, $prog);
+                        }
+                        self._raise('fileuploaded', [outData, pid, i]);
+                        if (!isBatch) {
+                            self.fileManager.remove($thumb);
+                        } else {
+                            updateUploadLog();
+                        }
+                    } else {
+                        uploadFailed = true;
+                        errMsg = self._parseError(op, jqXHR, self.msgUploadError, self.fileManager.getFileName(id));
+                        self._showFileError(errMsg, params);
+                        self._setPreviewError($thumb, true);
+                        if (!self.retryErrorUploads) {
+                            $btnUpload.hide();
+                        }
+                        if (isBatch) {
+                            updateUploadLog();
+                        }
+                        self._setProgress(101, $('#' + pid).find('.file-thumb-progress'), self.msgUploadError);
+                    }
+                }, self.processDelay);
+            };
+            fnComplete = function () {
+                setTimeout(function () {
+                    if (self.showPreview) {
+                        $btnUpload.removeAttr('disabled');
+                        $btnDelete.removeAttr('disabled');
+                        $thumb.removeClass('file-uploading');
+                    }
+                    if (!isBatch) {
+                        self.unlock(false);
+                        self._clearFileInput();
+                    } else {
+                        chkComplete();
+                    }
+                    self._initSuccessThumbs();
+                }, self.processDelay);
+            };
+            fnError = function (jqXHR, textStatus, errorThrown) {
+                errMsg = self._parseError(op, jqXHR, errorThrown, self.fileManager.getFileName(id));
+                uploadFailed = true;
+                setTimeout(function () {
+                    if (isBatch) {
+                        updateUploadLog();
+                    }
+                    self.fileManager.setProgress(id, 100);
+                    self._setPreviewError($thumb, true);
+                    if (!self.retryErrorUploads) {
+                        $btnUpload.hide();
+                    }
+                    $.extend(true, params, self._getOutData(formdata, jqXHR));
+                    self._setProgress(101, $prog, self.msgAjaxProgressError.replace('{operation}', op));
+                    self._setProgress(101, $thumb.find('.file-thumb-progress'), self.msgUploadError);
+                    self._showFileError(errMsg, params);
+                }, self.processDelay);
+            };
+            formdata.append(self.uploadFileAttr, fileObj.file, fileName);
+            self._setUploadData(formdata, {fileId: id});
+            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata, id, i);
+        },
+        _uploadBatch: function () {
+            var self = this, fm = self.fileManager, total = fm.total(), params = {}, fnBefore, fnSuccess, fnError,
+                fnComplete, hasPostData = total > 0 || !$.isEmptyObject(self.uploadExtraData), errMsg,
+                setAllUploaded, formdata = new FormData(), op = self.ajaxOperations.uploadBatch;
+            if (total === 0 || !hasPostData || self._abort(params)) {
+                return;
+            }
+            setAllUploaded = function () {
+                self.fileManager.clear();
+                self._clearFileInput();
+            };
+            fnBefore = function (jqXHR) {
+                self.lock();
+                fm.initStats();
+                var outData = self._getOutData(formdata, jqXHR);
+                self.ajaxAborted = false;
+                if (self.showPreview) {
+                    self._getThumbs().each(function () {
+                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
+                            $btnDelete = $thumb.find('.kv-file-remove');
+                        if (!$thumb.hasClass('file-preview-success')) {
+                            self._setThumbStatus($thumb, 'Loading');
+                            $h.addCss($thumb, 'file-uploading');
+                        }
+                        $btnUpload.attr('disabled', true);
+                        $btnDelete.attr('disabled', true);
+                    });
+                }
+                self._raise('filebatchpreupload', [outData]);
+                if (self._abort(outData)) {
+                    jqXHR.abort();
+                    self._getThumbs().each(function () {
+                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
+                            $btnDelete = $thumb.find('.kv-file-remove');
+                        if ($thumb.hasClass('file-preview-loading')) {
+                            self._setThumbStatus($thumb, 'New');
+                            $thumb.removeClass('file-uploading');
+                        }
+                        $btnUpload.removeAttr('disabled');
+                        $btnDelete.removeAttr('disabled');
+                    });
+                    self._setProgressCancelled();
+                }
+            };
+            fnSuccess = function (data, textStatus, jqXHR) {
+                /** @namespace data.errorkeys */
+                var outData = self._getOutData(formdata, jqXHR, data), key = 0,
+                    $thumbs = self._getThumbs(':not(.file-preview-success)'),
+                    keys = $h.isEmpty(data) || $h.isEmpty(data.errorkeys) ? [] : data.errorkeys;
+
+                if ($h.isEmpty(data) || $h.isEmpty(data.error)) {
+                    self._raise('filebatchuploadsuccess', [outData]);
+                    setAllUploaded();
+                    if (self.showPreview) {
+                        $thumbs.each(function () {
+                            var $thumb = $(this);
+                            self._setThumbStatus($thumb, 'Success');
+                            $thumb.removeClass('file-uploading');
+                            $thumb.find('.kv-file-upload').hide().removeAttr('disabled');
+                        });
+                        self._initUploadSuccess(data);
+                    } else {
+                        self.reset();
+                    }
+                    self._setProgress(101);
+                } else {
+                    if (self.showPreview) {
+                        $thumbs.each(function () {
+                            var $thumb = $(this);
+                            $thumb.removeClass('file-uploading');
+                            $thumb.find('.kv-file-upload').removeAttr('disabled');
+                            $thumb.find('.kv-file-remove').removeAttr('disabled');
+                            if (keys.length === 0 || $.inArray(key, keys) !== -1) {
+                                self._setPreviewError($thumb, true);
+                                if (!self.retryErrorUploads) {
+                                    $thumb.find('.kv-file-upload').hide();
+                                    self.fileManager.remove($thumb);
+                                }
+                            } else {
+                                $thumb.find('.kv-file-upload').hide();
+                                self._setThumbStatus($thumb, 'Success');
+                                self.fileManager.remove($thumb);
+                            }
+                            if (!$thumb.hasClass('file-preview-error') || self.retryErrorUploads) {
+                                key++;
+                            }
+                        });
+                        self._initUploadSuccess(data);
+                    }
+                    errMsg = self._parseError(op, jqXHR, self.msgUploadError);
+                    self._showFileError(errMsg, outData, 'filebatchuploaderror');
+                    self._setProgress(101, self.$progress, self.msgUploadError);
+                }
+            };
+            fnComplete = function () {
+                self.unlock();
+                self._initSuccessThumbs();
+                self._clearFileInput();
+                self._raise('filebatchuploadcomplete', [self.fileManager.stack, self._getExtraData()]);
+            };
+            fnError = function (jqXHR, textStatus, errorThrown) {
+                var outData = self._getOutData(formdata, jqXHR);
+                errMsg = self._parseError(op, jqXHR, errorThrown);
+                self._showFileError(errMsg, outData, 'filebatchuploaderror');
+                self.uploadFileCount = total - 1;
+                if (!self.showPreview) {
+                    return;
+                }
+                self._getThumbs().each(function () {
+                    var $thumb = $(this);
+                    $thumb.removeClass('file-uploading');
+                    if (self.fileManager.getFile($thumb.attr('data-fileid'))) {
+                        self._setPreviewError($thumb);
+                    }
+                });
+                self._getThumbs().removeClass('file-uploading');
+                self._getThumbs(' .kv-file-upload').removeAttr('disabled');
+                self._getThumbs(' .kv-file-delete').removeAttr('disabled');
+                self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op));
+            };
+            var ctr = 0;
+            $.each(self.fileManager.stack, function (key, data) {
+                if (!$h.isEmpty(data.file)) {
+                    formdata.append(self.uploadFileAttr, data.file, (data.nameFmt || ('untitled_' + ctr)));
+                }
+                ctr++;
+            });
+            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata);
+        },
+        _uploadExtraOnly: function () {
+            var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError, formdata = new FormData(), errMsg,
+                op = self.ajaxOperations.uploadExtra;
+            if (self._abort(params)) {
+                return;
+            }
+            fnBefore = function (jqXHR) {
+                self.lock();
+                var outData = self._getOutData(formdata, jqXHR);
+                self._raise('filebatchpreupload', [outData]);
+                self._setProgress(50);
+                params.data = outData;
+                params.xhr = jqXHR;
+                if (self._abort(params)) {
+                    jqXHR.abort();
+                    self._setProgressCancelled();
+                }
+            };
+            fnSuccess = function (data, textStatus, jqXHR) {
+                var outData = self._getOutData(formdata, jqXHR, data);
+                if ($h.isEmpty(data) || $h.isEmpty(data.error)) {
+                    self._raise('filebatchuploadsuccess', [outData]);
+                    self._clearFileInput();
+                    self._initUploadSuccess(data);
+                    self._setProgress(101);
+                } else {
+                    errMsg = self._parseError(op, jqXHR, self.msgUploadError);
+                    self._showFileError(errMsg, outData, 'filebatchuploaderror');
+                }
+            };
+            fnComplete = function () {
+                self.unlock();
+                self._clearFileInput();
+                self._raise('filebatchuploadcomplete', [self.fileManager.stack, self._getExtraData()]);
+            };
+            fnError = function (jqXHR, textStatus, errorThrown) {
+                var outData = self._getOutData(formdata, jqXHR);
+                errMsg = self._parseError(op, jqXHR, errorThrown);
+                params.data = outData;
+                self._showFileError(errMsg, outData, 'filebatchuploaderror');
+                self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op));
+            };
+            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata);
+        },
+        _deleteFileIndex: function ($frame) {
+            var self = this, ind = $frame.attr('data-fileindex'), rev = self.reversePreviewOrder;
+            if (ind.substring(0, 5) === 'init_') {
+                ind = parseInt(ind.replace('init_', ''));
+                self.initialPreview = $h.spliceArray(self.initialPreview, ind, rev);
+                self.initialPreviewConfig = $h.spliceArray(self.initialPreviewConfig, ind, rev);
+                self.initialPreviewThumbTags = $h.spliceArray(self.initialPreviewThumbTags, ind, rev);
+                self.getFrames().each(function () {
+                    var $nFrame = $(this), nInd = $nFrame.attr('data-fileindex');
+                    if (nInd.substring(0, 5) === 'init_') {
+                        nInd = parseInt(nInd.replace('init_', ''));
+                        if (nInd > ind) {
+                            nInd--;
+                            $nFrame.attr('data-fileindex', 'init_' + nInd);
+                        }
+                    }
+                });
+                if (self.uploadAsync || self.enableResumableUpload) {
+                    self.cacheInitialPreview = self.getPreview();
+                }
+            }
+        },
+        _initFileActions: function () {
+            var self = this, $preview = self.$preview;
+            if (!self.showPreview) {
+                return;
+            }
+            self._initZoomButton();
+            self.getFrames(' .kv-file-remove').each(function () {
+                var $el = $(this), $frame = $el.closest($h.FRAMES), hasError, id = $frame.attr('id'),
+                    ind = $frame.attr('data-fileindex'), n, cap, status;
+                self._handler($el, 'click', function () {
+                    status = self._raise('filepreremove', [id, ind]);
+                    if (status === false || !self._validateMinCount()) {
+                        return false;
+                    }
+                    hasError = $frame.hasClass('file-preview-error');
+                    $h.cleanMemory($frame);
+                    $frame.fadeOut('slow', function () {
+                        $h.cleanZoomCache($preview.find('#zoom-' + id));
+                        self.fileManager.remove($frame);
+                        self._clearObjects($frame);
+                        $frame.remove();
+                        if (id && hasError) {
+                            self.$errorContainer.find('li[data-thumb-id="' + id + '"]').fadeOut('fast', function () {
+                                $(this).remove();
+                                if (!self._errorsExist()) {
+                                    self._resetErrors();
+                                }
+                            });
+                        }
+                        self._clearFileInput();
+                        var chk = self.previewCache.count(true), len = self.fileManager.count(),
+                            file, hasThumb = self.showPreview && self.getFrames().length;
+                        if (len === 0 && chk === 0 && !hasThumb) {
+                            self.reset();
+                        } else {
+                            n = chk + len;
+                            if (n > 1) {
+                                cap = self._getMsgSelected(n);
+                            } else {
+                                file = self.fileManager.getFirstFile();
+                                cap = file ? file.nameFmt : '_';
+                            }
+                            self._setCaption(cap);
+                        }
+                        self._raise('fileremoved', [id, ind]);
+                    });
+                });
+            });
+            self.getFrames(' .kv-file-upload').each(function () {
+                var $el = $(this);
+                self._handler($el, 'click', function () {
+                    var $frame = $el.closest($h.FRAMES), id = $frame.attr('data-fileid');
+                    self.$progress.hide();
+                    if ($frame.hasClass('file-preview-error') && !self.retryErrorUploads) {
+                        return;
+                    }
+                    self._uploadSingle(self.fileManager.getIndex(id), id, false);
+                });
+            });
+        },
+        _initPreviewActions: function () {
+            var self = this, $preview = self.$preview, deleteExtraData = self.deleteExtraData || {},
+                btnRemove = $h.FRAMES + ' .kv-file-remove', settings = self.fileActionSettings,
+                origClass = settings.removeClass, errClass = settings.removeErrorClass,
+                resetProgress = function () {
+                    var hasFiles = self.isAjaxUpload ? self.previewCache.count(true) : self._inputFileCount();
+                    if (!self.getFrames().length && !hasFiles) {
+                        self._setCaption('');
+                        self.reset();
+                        self.initialCaption = '';
+                    }
+                };
+            self._initZoomButton();
+            $preview.find(btnRemove).each(function () {
+                var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key'), errMsg, fnBefore,
+                    fnSuccess, fnError, op = self.ajaxOperations.deleteThumb;
+                if ($h.isEmpty(vUrl) || vKey === undefined) {
+                    return;
+                }
+                if (typeof vUrl === 'function') {
+                    vUrl = vUrl();
+                }
+                var $frame = $el.closest($h.FRAMES), cache = self.previewCache.data, settings, params, config,
+                    fileName, extraData, index = $frame.attr('data-fileindex');
+                index = parseInt(index.replace('init_', ''));
+                config = $h.isEmpty(cache.config) && $h.isEmpty(cache.config[index]) ? null : cache.config[index];
+                extraData = $h.isEmpty(config) || $h.isEmpty(config.extra) ? deleteExtraData : config.extra;
+                fileName = config.filename || config.caption || '';
+                if (typeof extraData === 'function') {
+                    extraData = extraData();
+                }
+                params = {id: $el.attr('id'), key: vKey, extra: extraData};
+                fnBefore = function (jqXHR) {
+                    self.ajaxAborted = false;
+                    self._raise('filepredelete', [vKey, jqXHR, extraData]);
+                    if (self._abort()) {
+                        jqXHR.abort();
+                    } else {
+                        $el.removeClass(errClass);
+                        $h.addCss($frame, 'file-uploading');
+                        $h.addCss($el, 'disabled ' + origClass);
+                    }
+                };
+                fnSuccess = function (data, textStatus, jqXHR) {
+                    var n, cap;
+                    if (!$h.isEmpty(data) && !$h.isEmpty(data.error)) {
+                        params.jqXHR = jqXHR;
+                        params.response = data;
+                        errMsg = self._parseError(op, jqXHR, self.msgDeleteError, fileName);
+                        self._showFileError(errMsg, params, 'filedeleteerror');
+                        $frame.removeClass('file-uploading');
+                        $el.removeClass('disabled ' + origClass).addClass(errClass);
+                        resetProgress();
+                        return;
+                    }
+                    $frame.removeClass('file-uploading').addClass('file-deleted');
+                    $frame.fadeOut('slow', function () {
+                        index = parseInt(($frame.attr('data-fileindex')).replace('init_', ''));
+                        self.previewCache.unset(index);
+                        self._deleteFileIndex($frame);
+                        n = self.previewCache.count(true);
+                        cap = n > 0 ? self._getMsgSelected(n) : '';
+                        self._setCaption(cap);
+                        self._raise('filedeleted', [vKey, jqXHR, extraData]);
+                        $h.cleanZoomCache($preview.find('#zoom-' + $frame.attr('id')));
+                        self._clearObjects($frame);
+                        $frame.remove();
+                        resetProgress();
+                    });
+                };
+                fnError = function (jqXHR, textStatus, errorThrown) {
+                    var errMsg = self._parseError(op, jqXHR, errorThrown, fileName);
+                    params.jqXHR = jqXHR;
+                    params.response = {};
+                    self._showFileError(errMsg, params, 'filedeleteerror');
+                    $frame.removeClass('file-uploading');
+                    $el.removeClass('disabled ' + origClass).addClass(errClass);
+                    resetProgress();
+                };
+                self._initAjaxSettings();
+                self._mergeAjaxCallback('beforeSend', fnBefore, 'delete');
+                self._mergeAjaxCallback('success', fnSuccess, 'delete');
+                self._mergeAjaxCallback('error', fnError, 'delete');
+                settings = $.extend(true, {}, {
+                    url: self._encodeURI(vUrl),
+                    type: 'POST',
+                    dataType: 'json',
+                    data: $.extend(true, {}, {key: vKey}, extraData)
+                }, self._ajaxDeleteSettings);
+                self._handler($el, 'click', function () {
+                    if (!self._validateMinCount()) {
+                        return false;
+                    }
+                    self.ajaxAborted = false;
+                    self._raise('filebeforedelete', [vKey, extraData]);
+                    //noinspection JSUnresolvedVariable,JSHint
+                    if (self.ajaxAborted instanceof Promise) {
+                        self.ajaxAborted.then(function (result) {
+                            if (!result) {
+                                $.ajax(settings);
+                            }
+                        });
+                    } else {
+                        if (!self.ajaxAborted) {
+                            $.ajax(settings);
+                        }
+                    }
+                });
+            });
+        },
+        _hideFileIcon: function () {
+            var self = this;
+            if (self.overwriteInitial) {
+                self.$captionContainer.removeClass('icon-visible');
+            }
+        },
+        _showFileIcon: function () {
+            var self = this;
+            $h.addCss(self.$captionContainer, 'icon-visible');
+        },
+        _getSize: function (bytes, sizes) {
+            var self = this, size = parseFloat(bytes), i, func = self.fileSizeGetter, out;
+            if (!$.isNumeric(bytes) || !$.isNumeric(size)) {
+                return '';
+            }
+            if (typeof func === 'function') {
+                out = func(size);
+            } else {
+                if (size === 0) {
+                    out = '0.00 B';
+                } else {
+                    i = Math.floor(Math.log(size) / Math.log(1024));
+                    if (!sizes) {
+                        sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+                    }
+                    out = (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
+                }
+            }
+            return self._getLayoutTemplate('size').replace('{sizeText}', out);
+        },
+        _getFileType: function (ftype) {
+            var self = this;
+            return self.mimeTypeAliases[ftype] || ftype;
+        },
+        _generatePreviewTemplate: function (
+            cat,
+            data,
+            fname,
+            ftype,
+            previewId,
+            fileId,
+            isError,
+            size,
+            frameClass,
+            foot,
+            ind,
+            templ,
+            attrs,
+            zoomData
+        ) {
+            var self = this, caption = self.slug(fname), prevContent, zoomContent = '', styleAttribs = '',
+                screenW = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
+                config, newCat = self.preferIconicPreview ? 'other' : cat, title = caption, alt = caption,
+                footer = foot || self._renderFileFooter(cat, caption, size, 'auto', isError),
+                hasIconSetting = self._getPreviewIcon(fname), typeCss = 'type-default',
+                forcePrevIcon = hasIconSetting && self.preferIconicPreview,
+                forceZoomIcon = hasIconSetting && self.preferIconicZoomPreview, getContent;
+            config = screenW < 400 ? (self.previewSettingsSmall[newCat] || self.defaults.previewSettingsSmall[newCat]) :
+                (self.previewSettings[newCat] || self.defaults.previewSettings[newCat]);
+            if (config) {
+                $.each(config, function (key, val) {
+                    styleAttribs += key + ':' + val + ';';
+                });
+            }
+            getContent = function (c, d, zoom, frameCss) {
+                var id = zoom ? 'zoom-' + previewId : previewId, tmplt = self._getPreviewTemplate(c),
+                    css = (frameClass || '') + ' ' + frameCss;
+                if (self.frameClass) {
+                    css = self.frameClass + ' ' + css;
+                }
+                if (zoom) {
+                    css = css.replace(' ' + $h.SORT_CSS, '');
+                }
+                tmplt = self._parseFilePreviewIcon(tmplt, fname);
+                if (c === 'text') {
+                    d = $h.htmlEncode(d);
+                }
+                if (cat === 'object' && !ftype) {
+                    $.each(self.defaults.fileTypeSettings, function (key, func) {
+                        if (key === 'object' || key === 'other') {
+                            return;
+                        }
+                        if (func(fname, ftype)) {
+                            typeCss = 'type-' + key;
+                        }
+                    });
+                }
+                if (!$h.isEmpty(attrs)) {
+                    if (attrs.title !== undefined && attrs.title !== null) {
+                        title = attrs.title;
+                    }
+                    if (attrs.alt !== undefined && attrs.alt !== null) {
+                        title = attrs.alt;
+                    }
+                }
+                return tmplt.setTokens({
+                    'previewId': id,
+                    'caption': caption,
+                    'title': title,
+                    'alt': alt,
+                    'frameClass': css,
+                    'type': self._getFileType(ftype),
+                    'fileindex': ind,
+                    'fileid': fileId || '',
+                    'typeCss': typeCss,
+                    'footer': footer,
+                    'data': d,
+                    'template': templ || cat,
+                    'style': styleAttribs ? 'style="' + styleAttribs + '"' : ''
+                });
+            };
+            ind = ind || previewId.slice(previewId.lastIndexOf('-') + 1);
+            if (self.fileActionSettings.showZoom) {
+                zoomContent = getContent((forceZoomIcon ? 'other' : cat), zoomData ? zoomData : data, true,
+                    'kv-zoom-thumb');
+            }
+            zoomContent = '\n' + self._getLayoutTemplate('zoomCache').replace('{zoomContent}', zoomContent);
+            if (typeof self.sanitizeZoomCache === 'function') {
+                zoomContent = self.sanitizeZoomCache(zoomContent);
+            }
+            prevContent = getContent((forcePrevIcon ? 'other' : cat), data, false, 'kv-preview-thumb');
+            return prevContent + zoomContent;
+        },
+        _addToPreview: function ($preview, content) {
+            var self = this;
+            return self.reversePreviewOrder ? $preview.prepend(content) : $preview.append(content);
+        },
+        _previewDefault: function (file, previewId, isDisabled) {
+            var self = this, $preview = self.$preview;
+            if (!self.showPreview) {
+                return;
+            }
+            var fname = $h.getFileName(file), ftype = file ? file.type : '', content, size = file.size || 0,
+                caption = self._getFileName(file, ''), isError = isDisabled === true && !self.isAjaxUpload,
+                data = $h.createObjectURL(file), fileId = self.fileManager.getId(file);
+            self._clearDefaultPreview();
+            content = self._generatePreviewTemplate('other', data, fname, ftype, previewId, fileId, isError, size);
+            self._addToPreview($preview, content);
+            self._setThumbAttr(previewId, caption, size);
+            if (isDisabled === true && self.isAjaxUpload) {
+                self._setThumbStatus($('#' + previewId), 'Error');
+            }
+        },
+        canPreview: function (file) {
+            var self = this;
+            if (!file || !self.showPreview || !self.$preview || !self.$preview.length) {
+                return false;
+            }
+            var name = file.name || '', type = file.type || '', size = (file.size || 0) / 1000,
+                cat = self._parseFileType(type, name), allowedTypes, allowedMimes, allowedExts, skipPreview,
+                types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes,
+                exts = self.allowedPreviewExtensions || [], dTypes = self.disabledPreviewTypes,
+                dMimes = self.disabledPreviewMimeTypes, dExts = self.disabledPreviewExtensions || [],
+                maxSize = self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize) || 0,
+                expAllExt = new RegExp('\\.(' + exts.join('|') + ')$', 'i'),
+                expDisExt = new RegExp('\\.(' + dExts.join('|') + ')$', 'i');
+            allowedTypes = !types || types.indexOf(cat) !== -1;
+            allowedMimes = !mimes || mimes.indexOf(type) !== -1;
+            allowedExts = !exts.length || $h.compare(name, expAllExt);
+            skipPreview = (dTypes && dTypes.indexOf(cat) !== -1) || (dMimes && dMimes.indexOf(type) !== -1) ||
+                (dExts.length && $h.compare(name, expDisExt)) || (maxSize && !isNaN(maxSize) && size > maxSize);
+            return !skipPreview && (allowedTypes || allowedMimes || allowedExts);
+        },
+        _previewFile: function (i, file, theFile, previewId, data, fileInfo) {
+            if (!this.showPreview) {
+                return;
+            }
+            var self = this, fname = $h.getFileName(file), ftype = fileInfo.type, caption = fileInfo.name,
+                cat = self._parseFileType(ftype, fname), content, $preview = self.$preview, fsize = file.size || 0,
+                iData = (cat === 'text' || cat === 'html' || cat === 'image') ? theFile.target.result : data,
+                fileId = self.fileManager.getId(file);
+            /** @namespace window.DOMPurify */
+            if (cat === 'html' && self.purifyHtml && window.DOMPurify) {
+                iData = window.DOMPurify.sanitize(iData);
+            }
+            content = self._generatePreviewTemplate(cat, iData, fname, ftype, previewId, fileId, false, fsize);
+            self._clearDefaultPreview();
+            self._addToPreview($preview, content);
+            var $thumb = $preview.find('#' + previewId), $img = $thumb.find('img'), id = $thumb.attr('data-fileid');
+            self._validateImageOrientation($img, file, previewId, id, caption, ftype, fsize, iData);
+            self._setThumbAttr(previewId, caption, fsize);
+            self._initSortable();
+        },
+        _setThumbAttr: function (id, caption, size) {
+            var self = this, $frame = $('#' + id);
+            if ($frame.length) {
+                size = size && size > 0 ? self._getSize(size) : '';
+                $frame.data({'caption': caption, 'size': size});
+            }
+        },
+        _setInitThumbAttr: function () {
+            var self = this, data = self.previewCache.data, len = self.previewCache.count(true), config,
+                caption, size, previewId;
+            if (len === 0) {
+                return;
+            }
+            for (var i = 0; i < len; i++) {
+                config = data.config[i];
+                previewId = self.previewInitId + '-' + 'init_' + i;
+                caption = $h.ifSet('caption', config, $h.ifSet('filename', config));
+                size = $h.ifSet('size', config);
+                self._setThumbAttr(previewId, caption, size);
+            }
+        },
+        _slugDefault: function (text) {
+            // noinspection RegExpRedundantEscape
+            return $h.isEmpty(text) ? '' : String(text).replace(/[\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, '_');
+        },
+        _updateFileDetails: function (numFiles) {
+            var self = this, $el = self.$element, label, n, log, nFiles, file,
+                name = ($h.isIE(9) && $h.findFileName($el.val())) || ($el[0].files[0] && $el[0].files[0].name);
+            if (!name && self.fileManager.count() > 0) {
+                file = self.fileManager.getFirstFile();
+                label = file.nameFmt;
+            } else {
+                label = name ? self.slug(name) : '_';
+            }
+            n = self.isAjaxUpload ? self.fileManager.count() : numFiles;
+            nFiles = self.previewCache.count(true) + n;
+            log = n === 1 ? label : self._getMsgSelected(nFiles);
+            if (self.isError) {
+                self.$previewContainer.removeClass('file-thumb-loading');
+                self.$previewStatus.html('');
+                self.$captionContainer.removeClass('icon-visible');
+            } else {
+                self._showFileIcon();
+            }
+            self._setCaption(log, self.isError);
+            self.$container.removeClass('file-input-new file-input-ajax-new');
+            if (arguments.length === 1) {
+                self._raise('fileselect', [numFiles, label]);
+            }
+            if (self.previewCache.count(true)) {
+                self._initPreviewActions();
+            }
+        },
+        _setThumbStatus: function ($thumb, status) {
+            var self = this;
+            if (!self.showPreview) {
+                return;
+            }
+            var icon = 'indicator' + status, msg = icon + 'Title',
+                css = 'file-preview-' + status.toLowerCase(),
+                $indicator = $thumb.find('.file-upload-indicator'),
+                config = self.fileActionSettings;
+            $thumb.removeClass('file-preview-success file-preview-error file-preview-paused file-preview-loading');
+            if (status === 'Success') {
+                $thumb.find('.file-drag-handle').remove();
+            }
+            $indicator.html(config[icon]);
+            $indicator.attr('title', config[msg]);
+            $thumb.addClass(css);
+            if (status === 'Error' && !self.retryErrorUploads) {
+                $thumb.find('.kv-file-upload').attr('disabled', true);
+            }
+        },
+        _setProgressCancelled: function () {
+            var self = this;
+            self._setProgress(101, self.$progress, self.msgCancelled);
+        },
+        _setProgress: function (p, $el, error, stats) {
+            var self = this;
+            $el = $el || self.$progress;
+            if (!$el.length) {
+                return;
+            }
+            var pct = Math.min(p, 100), out, pctLimit = self.progressUploadThreshold,
+                t = p <= 100 ? self.progressTemplate : self.progressCompleteTemplate,
+                template = pct < 100 ? self.progressTemplate :
+                    (error ? (self.paused ? self.progressPauseTemplate : self.progressErrorTemplate) : t);
+            if (p >= 100) {
+                stats = '';
+            }
+            if (!$h.isEmpty(template)) {
+                if (pctLimit && pct > pctLimit && p <= 100) {
+                    out = template.setTokens({'percent': pctLimit, 'status': self.msgUploadThreshold});
+                } else {
+                    out = template.setTokens({'percent': pct, 'status': (p > 100 ? self.msgUploadEnd : pct + '%')});
+                }
+                stats = stats || '';
+                out = out.setTokens({stats: stats});
+                $el.html(out);
+                if (error) {
+                    $el.find('[role="progressbar"]').html(error);
+                }
+            }
+        },
+        _setFileDropZoneTitle: function () {
+            var self = this, $zone = self.$container.find('.file-drop-zone'), title = self.dropZoneTitle, strFiles;
+            if (self.isClickable) {
+                strFiles = $h.isEmpty(self.$element.attr('multiple')) ? self.fileSingle : self.filePlural;
+                title += self.dropZoneClickTitle.replace('{files}', strFiles);
+            }
+            $zone.find('.' + self.dropZoneTitleClass).remove();
+            if (!self.showPreview || $zone.length === 0 || self.fileManager.count() > 0 || !self.dropZoneEnabled ||
+                (!self.isAjaxUpload && self.$element.files)) {
+                return;
+            }
+            if ($zone.find($h.FRAMES).length === 0 && $h.isEmpty(self.defaultPreviewContent)) {
+                $zone.prepend('<div class="' + self.dropZoneTitleClass + '">' + title + '</div>');
+            }
+            self.$container.removeClass('file-input-new');
+            $h.addCss(self.$container, 'file-input-ajax-new');
+        },
+        _getStats: function (stats) {
+            var self = this, pendingTime, t;
+            if (!self.showUploadStats || !stats || !stats.bitrate) {
+                return '';
+            }
+            t = self._getLayoutTemplate('stats');
+            pendingTime = (!stats.elapsed || !stats.bps) ? self.msgCalculatingTime :
+                self.msgPendingTime.setTokens({time: $h.getElapsed(Math.ceil(stats.pendingBytes / stats.bps))});
+
+            return t.setTokens({
+                uploadSpeed: stats.bitrate,
+                pendingTime: pendingTime
+            });
+        },
+        _setResumableProgress: function (pct, stats, $thumb) {
+            var self = this, rm = self.resumableManager, obj = $thumb ? rm : self,
+                $prog = $thumb ? $thumb.find('.file-thumb-progress') : null;
+            if (obj.lastProgress === 0) {
+                obj.lastProgress = pct;
+            }
+            if (pct < obj.lastProgress) {
+                pct = obj.lastProgress;
+            }
+            self._setProgress(pct, $prog, null, self._getStats(stats));
+            obj.lastProgress = pct;
+        },
+        _setFileUploadStats: function (id, pct, total, stats) {
+            var self = this, $prog = self.$progress;
+            if (!self.showPreview && (!$prog || !$prog.length)) {
+                return;
+            }
+            var fm = self.fileManager, $thumb = fm.getThumb(id), pctTot, rm = self.resumableManager,
+                totUpSize = 0, totSize = fm.getTotalSize(), totStats = $.extend(true, {}, stats);
+            if (self.enableResumableUpload) {
+                var loaded = stats.loaded, currUplSize = rm.getUploadedSize(), currTotSize = rm.file.size, totLoaded;
+                loaded += currUplSize;
+                totLoaded = fm.uploadedSize + loaded;
+                pct = $h.round(100 * loaded / currTotSize);
+                stats.pendingBytes = currTotSize - currUplSize;
+                self._setResumableProgress(pct, stats, $thumb);
+                pctTot = Math.floor(100 * totLoaded / totSize);
+                totStats.pendingBytes = totSize - totLoaded;
+                self._setResumableProgress(pctTot, totStats);
+            } else {
+                fm.setProgress(id, pct);
+                $prog = $thumb && $thumb.length ? $thumb.find('.file-thumb-progress') : null;
+                self._setProgress(pct, $prog, null, self._getStats(stats));
+                $.each(fm.stats, function (id, cfg) {
+                    totUpSize += cfg.loaded;
+                });
+                totStats.pendingBytes = totSize - totUpSize;
+                pctTot = $h.round(totUpSize / totSize * 100);
+                self._setProgress(pctTot, null, null, self._getStats(totStats));
+            }
+        },
+        _validateMinCount: function () {
+            var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount();
+            if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(len - 1) < self.minFileCount) {
+                self._noFilesError({});
+                return false;
+            }
+            return true;
+        },
+        _getFileCount: function (fileCount) {
+            var self = this, addCount = 0;
+            if (self.validateInitialCount && !self.overwriteInitial) {
+                addCount = self.previewCache.count(true);
+                fileCount += addCount;
+            }
+            return fileCount;
+        },
+        _getFileId: function (file) {
+            return $h.getFileId(file, this.generateFileId);
+        },
+        _getFileName: function (file, defaultValue) {
+            var self = this, fileName = $h.getFileName(file);
+            return fileName ? self.slug(fileName) : defaultValue;
+        },
+        _getFileNames: function (skipNull) {
+            var self = this;
+            return self.filenames.filter(function (n) {
+                return (skipNull ? n !== undefined : n !== undefined && n !== null);
+            });
+        },
+        _setPreviewError: function ($thumb, keepFile) {
+            var self = this, removeFrame = self.removeFromPreviewOnError && !self.retryErrorUploads;
+            if (!keepFile || removeFrame) {
+                self.fileManager.remove($thumb);
+            }
+            if (!self.showPreview) {
+                return;
+            }
+            if (removeFrame) {
+                $thumb.remove();
+                return;
+            } else {
+                self._setThumbStatus($thumb, 'Error');
+            }
+            self._refreshUploadButton($thumb);
+        },
+        _refreshUploadButton: function ($thumb) {
+            var self = this, $btn = $thumb.find('.kv-file-upload'), cfg = self.fileActionSettings,
+                icon = cfg.uploadIcon, title = cfg.uploadTitle;
+            if (!$btn.length) {
+                return;
+            }
+            if (self.retryErrorUploads) {
+                icon = cfg.uploadRetryIcon;
+                title = cfg.uploadRetryTitle;
+            }
+            $btn.attr('title', title).html(icon);
+        },
+        _checkDimensions: function (i, chk, $img, $thumb, fname, type, params) {
+            var self = this, msg, dim, tag = chk === 'Small' ? 'min' : 'max', limit = self[tag + 'Image' + type],
+                $imgEl, isValid;
+            if ($h.isEmpty(limit) || !$img.length) {
+                return;
+            }
+            $imgEl = $img[0];
+            dim = (type === 'Width') ? $imgEl.naturalWidth || $imgEl.width : $imgEl.naturalHeight || $imgEl.height;
+            isValid = chk === 'Small' ? dim >= limit : dim <= limit;
+            if (isValid) {
+                return;
+            }
+            msg = self['msgImage' + type + chk].setTokens({'name': fname, 'size': limit});
+            self._showFileError(msg, params);
+            self._setPreviewError($thumb);
+        },
+        _getExifObj: function (data) {
+            var self = this, exifObj = null, error = $h.logMessages.exifWarning;
+            if (data.slice(0, 23) !== 'data:image/jpeg;base64,' && data.slice(0, 22) !== 'data:image/jpg;base64,') {
+                exifObj = null;
+                return;
+            }
+            try {
+                exifObj = window.piexif ? window.piexif.load(data) : null;
+            } catch (err) {
+                exifObj = null;
+                error = err && err.message || '';
+            }
+            if (!exifObj) {
+                self._log($h.logMessages.badExifParser, {details: error});
+            }
+            return exifObj;
+        },
+        setImageOrientation: function ($img, $zoomImg, value, $thumb) {
+            var self = this, invalidImg = !$img || !$img.length, invalidZoomImg = !$zoomImg || !$zoomImg.length, $mark,
+                isHidden = false, $div, zoomOnly = invalidImg && $thumb && $thumb.attr('data-template') === 'image', ev;
+            if (invalidImg && invalidZoomImg) {
+                return;
+            }
+            ev = 'load.fileinputimageorient';
+            if (zoomOnly) {
+                $img = $zoomImg;
+                $zoomImg = null;
+                $img.css(self.previewSettings.image);
+                $div = $(document.createElement('div')).appendTo($thumb.find('.kv-file-content'));
+                $mark = $(document.createElement('span')).insertBefore($img);
+                $img.css('visibility', 'hidden').removeClass('file-zoom-detail').appendTo($div);
+            } else {
+                isHidden = !$img.is(':visible');
+            }
+            $img.off(ev).on(ev, function () {
+                if (isHidden) {
+                    self.$preview.removeClass('hide-content');
+                    $thumb.find('.kv-file-content').css('visibility', 'hidden');
+                }
+                var img = $img.get(0), zoomImg = $zoomImg && $zoomImg.length ? $zoomImg.get(0) : null,
+                    h = img.offsetHeight, w = img.offsetWidth, r = $h.getRotation(value);
+                if (isHidden) {
+                    $thumb.find('.kv-file-content').css('visibility', 'visible');
+                    self.$preview.addClass('hide-content');
+                }
+                $img.data('orientation', value);
+                if (zoomImg) {
+                    $zoomImg.data('orientation', value);
+                }
+                if (value < 5) {
+                    $h.setTransform(img, r);
+                    $h.setTransform(zoomImg, r);
+                    return;
+                }
+                var offsetAngle = Math.atan(w / h), origFactor = Math.sqrt(Math.pow(h, 2) + Math.pow(w, 2)),
+                    scale = !origFactor ? 1 : (h / Math.cos(Math.PI / 2 + offsetAngle)) / origFactor,
+                    s = ' scale(' + Math.abs(scale) + ')';
+                $h.setTransform(img, r + s);
+                $h.setTransform(zoomImg, r + s);
+                if (zoomOnly) {
+                    $img.css('visibility', 'visible').insertAfter($mark).addClass('file-zoom-detail');
+                    $mark.remove();
+                    $div.remove();
+                }
+            });
+        },
+        _validateImageOrientation: function ($img, file, previewId, fileId, caption, ftype, fsize, iData) {
+            var self = this, exifObj, value, autoOrientImage = self.autoOrientImage;
+            exifObj = autoOrientImage ? self._getExifObj(iData) : null;
+            value = exifObj ? exifObj['0th'][piexif.ImageIFD.Orientation] : null; // jshint ignore:line
+            if (!value) {
+                self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj);
+                return;
+            }
+            self.setImageOrientation($img, $('#zoom-' + previewId + ' img'), value, $('#' + previewId));
+            self._raise('fileimageoriented', {'$img': $img, 'file': file});
+            self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj);
+        },
+        _validateImage: function (previewId, fileId, fname, ftype, fsize, iData, exifObj) {
+            var self = this, $preview = self.$preview, params, w1, w2, $thumb = $preview.find('#' + previewId),
+                i = $thumb.attr('data-fileindex'), $img = $thumb.find('img');
+            fname = fname || 'Untitled';
+            $img.one('load', function () {
+                w1 = $thumb.width();
+                w2 = $preview.width();
+                if (w1 > w2) {
+                    $img.css('width', '100%');
+                }
+                params = {ind: i, id: previewId, fileId: fileId};
+                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Width', params);
+                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Height', params);
+                if (!self.resizeImage) {
+                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Width', params);
+                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Height', params);
+                }
+                self._raise('fileimageloaded', [previewId]);
+                self.fileManager.addImage(fileId, {
+                    ind: i,
+                    img: $img,
+                    thumb: $thumb,
+                    pid: previewId,
+                    typ: ftype,
+                    siz: fsize,
+                    validated: false,
+                    imgData: iData,
+                    exifObj: exifObj
+                });
+                $thumb.data('exif', exifObj);
+                self._validateAllImages();
+            }).one('error', function () {
+                self._raise('fileimageloaderror', [previewId]);
+            }).each(function () {
+                if (this.complete) {
+                    $(this).trigger('load');
+                } else {
+                    if (this.error) {
+                        $(this).trigger('error');
+                    }
+                }
+            });
+        },
+        _validateAllImages: function () {
+            var self = this, counter = {val: 0}, numImgs = self.fileManager.getImageCount(), fsize,
+                minSize = self.resizeIfSizeMoreThan;
+            if (numImgs !== self.fileManager.totalImages) {
+                return;
+            }
+            self._raise('fileimagesloaded');
+            if (!self.resizeImage) {
+                return;
+            }
+            $.each(self.fileManager.loadedImages, function (id, config) {
+                if (!config.validated) {
+                    fsize = config.siz;
+                    if (fsize && fsize > minSize * 1000) {
+                        self._getResizedImage(id, config, counter, numImgs);
+                    }
+                    config.validated = true;
+                }
+            });
+        },
+        _getResizedImage: function (id, config, counter, numImgs) {
+            var self = this, img = $(config.img)[0], width = img.naturalWidth, height = img.naturalHeight, blob,
+                ratio = 1, maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height,
+                isValidImage = !!(width && height), chkWidth, chkHeight, canvas = self.imageCanvas, dataURI,
+                context = self.imageCanvasContext, type = config.typ, pid = config.pid, ind = config.ind,
+                $thumb = config.thumb, throwError, msg, exifObj = config.exifObj, exifStr, file, params, evParams;
+            throwError = function (msg, params, ev) {
+                if (self.isAjaxUpload) {
+                    self._showFileError(msg, params, ev);
+                } else {
+                    self._showError(msg, params, ev);
+                }
+                self._setPreviewError($thumb);
+            };
+            file = self.fileManager.getFile(id);
+            params = {id: pid, 'index': ind, fileId: id};
+            evParams = [id, pid, ind];
+            if (!file || !isValidImage || (width <= maxWidth && height <= maxHeight)) {
+                if (isValidImage && file) {
+                    self._raise('fileimageresized', evParams);
+                }
+                counter.val++;
+                if (counter.val === numImgs) {
+                    self._raise('fileimagesresized');
+                }
+                if (!isValidImage) {
+                    throwError(self.msgImageResizeError, params, 'fileimageresizeerror');
+                    return;
+                }
+            }
+            type = type || self.resizeDefaultImageType;
+            chkWidth = width > maxWidth;
+            chkHeight = height > maxHeight;
+            if (self.resizePreference === 'width') {
+                ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1);
+            } else {
+                ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1);
+            }
+            self._resetCanvas();
+            width *= ratio;
+            height *= ratio;
+            canvas.width = width;
+            canvas.height = height;
+            try {
+                context.drawImage(img, 0, 0, width, height);
+                dataURI = canvas.toDataURL(type, self.resizeQuality);
+                if (exifObj) {
+                    exifStr = window.piexif.dump(exifObj);
+                    dataURI = window.piexif.insert(exifStr, dataURI);
+                }
+                blob = $h.dataURI2Blob(dataURI);
+                self.fileManager.setFile(id, blob);
+                self._raise('fileimageresized', evParams);
+                counter.val++;
+                if (counter.val === numImgs) {
+                    self._raise('fileimagesresized', [undefined, undefined]);
+                }
+                if (!(blob instanceof Blob)) {
+                    throwError(self.msgImageResizeError, params, 'fileimageresizeerror');
+                }
+            }
+            catch (err) {
+                counter.val++;
+                if (counter.val === numImgs) {
+                    self._raise('fileimagesresized', [undefined, undefined]);
+                }
+                msg = self.msgImageResizeException.replace('{errors}', err.message);
+                throwError(msg, params, 'fileimageresizeexception');
+            }
+        },
+        _initBrowse: function ($container) {
+            var self = this, $el = self.$element;
+            if (self.showBrowse) {
+                self.$btnFile = $container.find('.btn-file').append($el);
+            } else {
+                $el.appendTo($container).attr('tabindex', -1);
+                $h.addCss($el, 'file-no-browse');
+            }
+        },
+        _initClickable: function () {
+            var self = this, $zone, $tmpZone;
+            if (!self.isClickable) {
+                return;
+            }
+            $zone = self.$dropZone;
+            if (!self.isAjaxUpload) {
+                $tmpZone = self.$preview.find('.file-default-preview');
+                if ($tmpZone.length) {
+                    $zone = $tmpZone;
+                }
+            }
+
+            $h.addCss($zone, 'clickable');
+            $zone.attr('tabindex', -1);
+            self._handler($zone, 'click', function (e) {
+                var $tar = $(e.target);
+                if (!$(self.elErrorContainer + ':visible').length &&
+                    (!$tar.parents('.file-preview-thumbnails').length || $tar.parents(
+                        '.file-default-preview').length)) {
+                    self.$element.data('zoneClicked', true).trigger('click');
+                    $zone.blur();
+                }
+            });
+        },
+        _initCaption: function () {
+            var self = this, cap = self.initialCaption || '';
+            if (self.overwriteInitial || $h.isEmpty(cap)) {
+                self.$caption.val('');
+                return false;
+            }
+            self._setCaption(cap);
+            return true;
+        },
+        _setCaption: function (content, isError) {
+            var self = this, title, out, icon, n, cap, file;
+            if (!self.$caption.length) {
+                return;
+            }
+            self.$captionContainer.removeClass('icon-visible');
+            if (isError) {
+                title = $('<div>' + self.msgValidationError + '</div>').text();
+                n = self.fileManager.count();
+                if (n) {
+                    file = self.fileManager.getFirstFile();
+                    cap = n === 1 && file ? file.nameFmt : self._getMsgSelected(n);
+                } else {
+                    cap = self._getMsgSelected(self.msgNo);
+                }
+                out = $h.isEmpty(content) ? cap : content;
+                icon = '<span class="' + self.msgValidationErrorClass + '">' + self.msgValidationErrorIcon + '</span>';
+            } else {
+                if ($h.isEmpty(content)) {
+                    return;
+                }
+                title = $('<div>' + content + '</div>').text();
+                out = title;
+                icon = self._getLayoutTemplate('fileIcon');
+            }
+            self.$captionContainer.addClass('icon-visible');
+            self.$caption.attr('title', title).val(out);
+            self.$captionIcon.html(icon);
+        },
+        _createContainer: function () {
+            var self = this, attribs = {'class': 'file-input file-input-new' + (self.rtl ? ' kv-rtl' : '')},
+                $container = $(document.createElement('div')).attr(attribs).html(self._renderMain());
+            $container.insertBefore(self.$element);
+            self._initBrowse($container);
+            if (self.theme) {
+                $container.addClass('theme-' + self.theme);
+            }
+            return $container;
+        },
+        _refreshContainer: function () {
+            var self = this, $container = self.$container, $el = self.$element;
+            $el.insertAfter($container);
+            $container.html(self._renderMain());
+            self._initBrowse($container);
+            self._validateDisabled();
+        },
+        _validateDisabled: function () {
+            var self = this;
+            self.$caption.attr({readonly: self.isDisabled});
+        },
+        _renderMain: function () {
+            var self = this,
+                dropCss = self.dropZoneEnabled ? ' file-drop-zone' : 'file-drop-disabled',
+                close = !self.showClose ? '' : self._getLayoutTemplate('close'),
+                preview = !self.showPreview ? '' : self._getLayoutTemplate('preview')
+                    .setTokens({'class': self.previewClass, 'dropClass': dropCss}),
+                css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass,
+                caption = self.captionTemplate.setTokens({'class': css + ' kv-fileinput-caption'});
+            return self.mainTemplate.setTokens({
+                'class': self.mainClass + (!self.showBrowse && self.showCaption ? ' no-browse' : ''),
+                'preview': preview,
+                'close': close,
+                'caption': caption,
+                'upload': self._renderButton('upload'),
+                'remove': self._renderButton('remove'),
+                'cancel': self._renderButton('cancel'),
+                'pause': self._renderButton('pause'),
+                'browse': self._renderButton('browse')
+            });
+
+        },
+        _renderButton: function (type) {
+            var self = this, tmplt = self._getLayoutTemplate('btnDefault'), css = self[type + 'Class'],
+                title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'],
+                status = self.isDisabled ? ' disabled' : '', btnType = 'button';
+            switch (type) {
+                case 'remove':
+                    if (!self.showRemove) {
+                        return '';
+                    }
+                    break;
+                case 'cancel':
+                    if (!self.showCancel) {
+                        return '';
+                    }
+                    css += ' kv-hidden';
+                    break;
+                case 'pause':
+                    if (!self.showPause) {
+                        return '';
+                    }
+                    css += ' kv-hidden';
+                    break;
+                case 'upload':
+                    if (!self.showUpload) {
+                        return '';
+                    }
+                    if (self.isAjaxUpload && !self.isDisabled) {
+                        tmplt = self._getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl);
+                    } else {
+                        btnType = 'submit';
+                    }
+                    break;
+                case 'browse':
+                    if (!self.showBrowse) {
+                        return '';
+                    }
+                    tmplt = self._getLayoutTemplate('btnBrowse');
+                    break;
+                default:
+                    return '';
+            }
+
+            css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button';
+            if (!$h.isEmpty(label)) {
+                label = ' <span class="' + self.buttonLabelClass + '">' + label + '</span>';
+            }
+            return tmplt.setTokens({
+                'type': btnType, 'css': css, 'title': title, 'status': status, 'icon': icon, 'label': label
+            });
+        },
+        _renderThumbProgress: function () {
+            var self = this;
+            return '<div class="file-thumb-progress kv-hidden">' +
+                self.progressInfoTemplate.setTokens({percent: 101, status: self.msgUploadBegin, stats: ''}) +
+                '</div>';
+        },
+        _renderFileFooter: function (cat, caption, size, width, isError) {
+            var self = this, config = self.fileActionSettings, rem = config.showRemove, drg = config.showDrag,
+                upl = config.showUpload, zoom = config.showZoom, out, params,
+                template = self._getLayoutTemplate('footer'), tInd = self._getLayoutTemplate('indicator'),
+                ind = isError ? config.indicatorError : config.indicatorNew,
+                title = isError ? config.indicatorErrorTitle : config.indicatorNewTitle,
+                indicator = tInd.setTokens({'indicator': ind, 'indicatorTitle': title});
+            size = self._getSize(size);
+            params = {type: cat, caption: caption, size: size, width: width, progress: '', indicator: indicator};
+            if (self.isAjaxUpload) {
+                params.progress = self._renderThumbProgress();
+                params.actions = self._renderFileActions(params, upl, false, rem, zoom, drg, false, false, false);
+            } else {
+                params.actions = self._renderFileActions(params, false, false, false, zoom, drg, false, false, false);
+            }
+            out = template.setTokens(params);
+            out = $h.replaceTags(out, self.previewThumbTags);
+            return out;
+        },
+        _renderFileActions: function (
+            cfg,
+            showUpl,
+            showDwn,
+            showDel,
+            showZoom,
+            showDrag,
+            disabled,
+            url,
+            key,
+            isInit,
+            dUrl,
+            dFile
+        ) {
+            var self = this;
+            if (!cfg.type && isInit) {
+                cfg.type = 'image';
+            }
+            if (self.enableResumableUpload) {
+                showUpl = false;
+            } else {
+                if (typeof showUpl === 'function') {
+                    showUpl = showUpl(cfg);
+                }
+            }
+            if (typeof showDwn === 'function') {
+                showDwn = showDwn(cfg);
+            }
+            if (typeof showDel === 'function') {
+                showDel = showDel(cfg);
+            }
+            if (typeof showZoom === 'function') {
+                showZoom = showZoom(cfg);
+            }
+            if (typeof showDrag === 'function') {
+                showDrag = showDrag(cfg);
+            }
+            if (!showUpl && !showDwn && !showDel && !showZoom && !showDrag) {
+                return '';
+            }
+            var vUrl = url === false ? '' : ' data-url="' + url + '"', btnZoom = '', btnDrag = '', css,
+                vKey = key === false ? '' : ' data-key="' + key + '"', btnDelete = '', btnUpload = '', btnDownload = '',
+                template = self._getLayoutTemplate('actions'), config = self.fileActionSettings,
+                otherButtons = self.otherActionButtons.setTokens({'dataKey': vKey, 'key': key}),
+                removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass;
+            if (showDel) {
+                btnDelete = self._getLayoutTemplate('actionDelete').setTokens({
+                    'removeClass': removeClass,
+                    'removeIcon': config.removeIcon,
+                    'removeTitle': config.removeTitle,
+                    'dataUrl': vUrl,
+                    'dataKey': vKey,
+                    'key': key
+                });
+            }
+            if (showUpl) {
+                btnUpload = self._getLayoutTemplate('actionUpload').setTokens({
+                    'uploadClass': config.uploadClass,
+                    'uploadIcon': config.uploadIcon,
+                    'uploadTitle': config.uploadTitle
+                });
+            }
+            if (showDwn) {
+                btnDownload = self._getLayoutTemplate('actionDownload').setTokens({
+                    'downloadClass': config.downloadClass,
+                    'downloadIcon': config.downloadIcon,
+                    'downloadTitle': config.downloadTitle,
+                    'downloadUrl': dUrl || self.initialPreviewDownloadUrl
+                });
+                btnDownload = btnDownload.setTokens({'filename': dFile, 'key': key});
+            }
+            if (showZoom) {
+                btnZoom = self._getLayoutTemplate('actionZoom').setTokens({
+                    'zoomClass': config.zoomClass,
+                    'zoomIcon': config.zoomIcon,
+                    'zoomTitle': config.zoomTitle
+                });
+            }
+            if (showDrag && isInit) {
+                css = 'drag-handle-init ' + config.dragClass;
+                btnDrag = self._getLayoutTemplate('actionDrag').setTokens({
+                    'dragClass': css,
+                    'dragTitle': config.dragTitle,
+                    'dragIcon': config.dragIcon
+                });
+            }
+            return template.setTokens({
+                'delete': btnDelete,
+                'upload': btnUpload,
+                'download': btnDownload,
+                'zoom': btnZoom,
+                'drag': btnDrag,
+                'other': otherButtons
+            });
+        },
+        _browse: function (e) {
+            var self = this;
+            if (e && e.isDefaultPrevented() || !self._raise('filebrowse')) {
+                return;
+            }
+            if (self.isError && !self.isAjaxUpload) {
+                self.clear();
+            }
+            self.$captionContainer.focus();
+        },
+        _change: function (e) {
+            var self = this;
+            if (self.changeTriggered) {
+                return;
+            }
+            var $el = self.$element, isDragDrop = arguments.length > 1, isAjaxUpload = self.isAjaxUpload,
+                tfiles, files = isDragDrop ? arguments[1] : $el.get(0).files, total,
+                maxCount = !isAjaxUpload && $h.isEmpty($el.attr('multiple')) ? 1 : self.maxFileCount,
+                len, ctr = self.fileManager.count(), isSingleUpload = $h.isEmpty($el.attr('multiple')),
+                flagSingle = (isSingleUpload && ctr > 0),
+                throwError = function (mesg, file, previewId, index) {
+                    var p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files), {id: previewId, index: index}),
+                        p2 = {id: previewId, index: index, file: file, files: files};
+                    return isAjaxUpload ? self._showFileError(mesg, p1) : self._showError(mesg, p2);
+                },
+                maxCountCheck = function (n, m) {
+                    var msg = self.msgFilesTooMany.replace('{m}', m).replace('{n}', n);
+                    self.isError = throwError(msg, null, null, null);
+                    self.$captionContainer.removeClass('icon-visible');
+                    self._setCaption('', true);
+                    self.$container.removeClass('file-input-new file-input-ajax-new');
+                };
+            self.reader = null;
+            self._resetUpload();
+            self._hideFileIcon();
+            if (self.dropZoneEnabled) {
+                self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove();
+            }
+            if (!isAjaxUpload) {
+                if (e.target && e.target.files === undefined) {
+                    files = e.target.value ? [{name: e.target.value.replace(/^.+\\/, '')}] : [];
+                } else {
+                    files = e.target.files || {};
+                }
+            }
+            tfiles = files;
+            if ($h.isEmpty(tfiles) || tfiles.length === 0) {
+                if (!isAjaxUpload) {
+                    self.clear();
+                }
+                self._raise('fileselectnone');
+                return;
+            }
+            self._resetErrors();
+            len = tfiles.length;
+            total = self._getFileCount(isAjaxUpload ? (self.fileManager.count() + len) : len);
+            if (maxCount > 0 && total > maxCount) {
+                if (!self.autoReplace || len > maxCount) {
+                    maxCountCheck((self.autoReplace && len > maxCount ? len : total), maxCount);
+                    return;
+                }
+                if (total > maxCount) {
+                    self._resetPreviewThumbs(isAjaxUpload);
+                }
+            } else {
+                if (!isAjaxUpload || flagSingle) {
+                    self._resetPreviewThumbs(false);
+                    if (flagSingle) {
+                        self.clearFileStack();
+                    }
+                } else {
+                    if (isAjaxUpload && ctr === 0 && (!self.previewCache.count(true) || self.overwriteInitial)) {
+                        self._resetPreviewThumbs(true);
+                    }
+                }
+            }
+            self.readFiles(tfiles);
+        },
+        _abort: function (params) {
+            var self = this, data;
+            if (self.ajaxAborted && typeof self.ajaxAborted === 'object' && self.ajaxAborted.message !== undefined) {
+                data = $.extend(true, {}, self._getOutData(null), params);
+                data.abortData = self.ajaxAborted.data || {};
+                data.abortMessage = self.ajaxAborted.message;
+                self._setProgress(101, self.$progress, self.msgCancelled);
+                self._showFileError(self.ajaxAborted.message, data, 'filecustomerror');
+                self.cancel();
+                return true;
+            }
+            return !!self.ajaxAborted;
+        },
+        _resetFileStack: function () {
+            var self = this, i = 0;
+            self._getThumbs().each(function () {
+                var $thumb = $(this), ind = $thumb.attr('data-fileindex'), pid = $thumb.attr('id');
+                if (ind === '-1' || ind === -1) {
+                    return;
+                }
+                if (!self.fileManager.getFile($thumb.attr('data-fileid'))) {
+                    $thumb.attr({'id': self.previewInitId + '-' + i, 'data-fileindex': i});
+                    i++;
+                } else {
+                    $thumb.attr({'id': 'uploaded-' + $h.uniqId(), 'data-fileindex': '-1'});
+                }
+                self.$preview.find('#zoom-' + pid).attr({
+                    'id': 'zoom-' + $thumb.attr('id'),
+                    'data-fileindex': $thumb.attr('data-fileindex')
+                });
+            });
+        },
+        _isFileSelectionValid: function (cnt) {
+            var self = this;
+            cnt = cnt || 0;
+            if (self.required && !self.getFilesCount()) {
+                self.$errorContainer.html('');
+                self._showFileError(self.msgFileRequired);
+                return false;
+            }
+            if (self.minFileCount > 0 && self._getFileCount(cnt) < self.minFileCount) {
+                self._noFilesError({});
+                return false;
+            }
+            return true;
+        },
+        clearFileStack: function () {
+            var self = this;
+            self.fileManager.clear();
+            self._initResumableUpload();
+            if (self.enableResumableUpload) {
+                if (self.showPause === null) {
+                    self.showPause = true;
+                }
+                if (self.showCancel === null) {
+                    self.showCancel = false;
+                }
+            } else {
+                self.showPause = false;
+                if (self.showCancel === null) {
+                    self.showCancel = true;
+                }
+            }
+            return self.$element;
+        },
+        getFileStack: function () {
+            return this.fileManager.stack;
+        },
+        getFileList: function () {
+            return this.fileManager.list();
+        },
+        getFilesCount: function () {
+            var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount();
+            return self._getFileCount(len);
+        },
+        readFiles: function (files) {
+            this.reader = new FileReader();
+            var self = this, $el = self.$element, reader = self.reader,
+                $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading,
+                msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length,
+                settings = self.fileTypeSettings, ctr = self.fileManager.count(), readFile,
+                fileTypes = self.allowedFileTypes, typLen = fileTypes ? fileTypes.length : 0,
+                fileExt = self.allowedFileExtensions, strExt = $h.isEmpty(fileExt) ? '' : fileExt.join(', '),
+                throwError = function (msg, file, previewId, index, fileId) {
+                    var p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files),
+                        {id: previewId, index: index, fileId: fileId}), $thumb = $('#' + previewId),
+                        p2 = {id: previewId, index: index, fileId: fileId, file: file, files: files};
+                    self._previewDefault(file, previewId, true);
+                    if (self.isAjaxUpload) {
+                        setTimeout(function () {
+                            readFile(index + 1);
+                        }, self.processDelay);
+                    } else {
+                        numFiles = 0;
+                    }
+                    self._initFileActions();
+                    $thumb.remove();
+                    self.isError = self.isAjaxUpload ? self._showFileError(msg, p1) : self._showError(msg, p2);
+                    self._updateFileDetails(numFiles);
+                };
+            self.fileManager.clearImages();
+            $.each(files, function (key, file) {
+                var func = self.fileTypeSettings.image;
+                if (func && func(file.type)) {
+                    self.fileManager.totalImages++;
+                }
+            });
+            readFile = function (i) {
+                if ($h.isEmpty($el.attr('multiple'))) {
+                    numFiles = 1;
+                }
+                if (i >= numFiles) {
+                    if (self.isAjaxUpload && self.fileManager.count() > 0) {
+                        self._raise('filebatchselected', [self.fileManager.stack]);
+                    } else {
+                        self._raise('filebatchselected', [files]);
+                    }
+                    $container.removeClass('file-thumb-loading');
+                    $status.html('');
+                    return;
+                }
+                var node = ctr + i, previewId = previewInitId + '-' + node, file = files[i], fSizeKB, j, msg, $thumb,
+                    fnText = settings.text, fnImage = settings.image, fnHtml = settings.html, typ, chk, typ1, typ2,
+                    caption = self._getFileName(file, ''), fileSize = (file && file.size || 0) / 1000,
+                    fileExtExpr = '', previewData = $h.createObjectURL(file), fileCount = 0,
+                    strTypes = '', fileId,
+                    func, knownTypes = 0, isText, isHtml, isImage, txtFlag, processFileLoaded = function () {
+                        var msg = msgProgress.setTokens({
+                            'index': i + 1,
+                            'files': numFiles,
+                            'percent': 50,
+                            'name': caption
+                        });
+                        setTimeout(function () {
+                            $status.html(msg);
+                            self._updateFileDetails(numFiles);
+                            readFile(i + 1);
+                        }, self.processDelay);
+                        self._raise('fileloaded', [file, previewId, i, reader]);
+                    };
+                if (!file) {
+                    return;
+                }
+                fileId = self.fileManager.getId(file);
+                if (typLen > 0) {
+                    for (j = 0; j < typLen; j++) {
+                        typ1 = fileTypes[j];
+                        typ2 = self.msgFileTypes[typ1] || typ1;
+                        strTypes += j === 0 ? typ2 : ', ' + typ2;
+                    }
+                }
+                if (caption === false) {
+                    readFile(i + 1);
+                    return;
+                }
+                if (caption.length === 0) {
+                    msg = self.msgInvalidFileName.replace('{name}', $h.htmlEncode($h.getFileName(file), '[unknown]'));
+                    throwError(msg, file, previewId, i, fileId);
+                    return;
+                }
+                if (!$h.isEmpty(fileExt)) {
+                    fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i');
+                }
+                fSizeKB = fileSize.toFixed(2);
+                if (self.maxFileSize > 0 && fileSize > self.maxFileSize) {
+                    msg = self.msgSizeTooLarge.setTokens({
+                        'name': caption,
+                        'size': fSizeKB,
+                        'maxSize': self.maxFileSize
+                    });
+                    throwError(msg, file, previewId, i, fileId);
+                    return;
+                }
+                if (self.minFileSize !== null && fileSize <= $h.getNum(self.minFileSize)) {
+                    msg = self.msgSizeTooSmall.setTokens({
+                        'name': caption,
+                        'size': fSizeKB,
+                        'minSize': self.minFileSize
+                    });
+                    throwError(msg, file, previewId, i, fileId);
+                    return;
+                }
+                if (!$h.isEmpty(fileTypes) && $h.isArray(fileTypes)) {
+                    for (j = 0; j < fileTypes.length; j += 1) {
+                        typ = fileTypes[j];
+                        func = settings[typ];
+                        fileCount += !func || (typeof func !== 'function') ? 0 : (func(file.type,
+                            $h.getFileName(file)) ? 1 : 0);
+                    }
+                    if (fileCount === 0) {
+                        msg = self.msgInvalidFileType.setTokens({name: caption, types: strTypes});
+                        throwError(msg, file, previewId, i, fileId);
+                        return;
+                    }
+                }
+                if (fileCount === 0 && !$h.isEmpty(fileExt) && $h.isArray(fileExt) && !$h.isEmpty(fileExtExpr)) {
+                    chk = $h.compare(caption, fileExtExpr);
+                    fileCount += $h.isEmpty(chk) ? 0 : chk.length;
+                    if (fileCount === 0) {
+                        msg = self.msgInvalidFileExtension.setTokens({name: caption, extensions: strExt});
+                        throwError(msg, file, previewId, i, fileId);
+                        return;
+                    }
+                }
+                if (self.isAjaxUpload && self.fileManager.exists(fileId)) {
+                    msg = self.msgDuplicateFile.setTokens({name: caption, size: fSizeKB});
+                    throwError(msg, file, previewId, i, fileId);
+                    $thumb = $('#' + previewId);
+                    if ($thumb && $thumb.length) {
+                        $thumb.remove();
+                    }
+                    return;
+                }
+                if (!self.canPreview(file)) {
+                    if (self.isAjaxUpload) {
+                        self.fileManager.add(file);
+                    }
+                    if (self.showPreview) {
+                        $container.addClass('file-thumb-loading');
+                        self._previewDefault(file, previewId);
+                        self._initFileActions();
+                    }
+                    setTimeout(function () {
+                        self._updateFileDetails(numFiles);
+                        readFile(i + 1);
+                        self._raise('fileloaded', [file, previewId, i]);
+                    }, 10);
+                    return;
+                }
+                isText = fnText(file.type, caption);
+                isHtml = fnHtml(file.type, caption);
+                isImage = fnImage(file.type, caption);
+                $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles));
+                $container.addClass('file-thumb-loading');
+                reader.onerror = function (evt) {
+                    self._errorHandler(evt, caption);
+                };
+                reader.onload = function (theFile) {
+                    var hex, fileInfo, uint, byte, bytes = [], contents, mime, readTextImage = function (textFlag) {
+                        var newReader = new FileReader();
+                        newReader.onerror = function (theFileNew) {
+                            self._errorHandler(theFileNew, caption);
+                        };
+                        newReader.onload = function (theFileNew) {
+                            self._previewFile(i, file, theFileNew, previewId, previewData, fileInfo);
+                            self._initFileActions();
+                            processFileLoaded();
+                        };
+                        if (textFlag) {
+                            newReader.readAsText(file, self.textEncoding);
+                        } else {
+                            newReader.readAsDataURL(file);
+                        }
+                    };
+                    fileInfo = {'name': caption, 'type': file.type};
+                    $.each(settings, function (k, f) {
+                        if (k !== 'object' && k !== 'other' && typeof f === 'function' && f(file.type, caption)) {
+                            knownTypes++;
+                        }
+                    });
+                    if (knownTypes === 0) {// auto detect mime types from content if no known file types detected
+                        uint = new Uint8Array(theFile.target.result);
+                        for (j = 0; j < uint.length; j++) {
+                            byte = uint[j].toString(16);
+                            bytes.push(byte);
+                        }
+                        hex = bytes.join('').toLowerCase().substring(0, 8);
+                        mime = $h.getMimeType(hex, '', '');
+                        if ($h.isEmpty(mime)) { // look for ascii text content
+                            contents = $h.arrayBuffer2String(reader.result);
+                            mime = $h.isSvg(contents) ? 'image/svg+xml' : $h.getMimeType(hex, contents, file.type);
+                        }
+                        fileInfo = {'name': caption, 'type': mime};
+                        isText = fnText(mime, '');
+                        isHtml = fnHtml(mime, '');
+                        isImage = fnImage(mime, '');
+                        txtFlag = isText || isHtml;
+                        if (txtFlag || isImage) {
+                            readTextImage(txtFlag);
+                            return;
+                        }
+                    }
+                    self._previewFile(i, file, theFile, previewId, previewData, fileInfo);
+                    self._initFileActions();
+                    processFileLoaded();
+                };
+                reader.onprogress = function (data) {
+                    if (data.lengthComputable) {
+                        var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact);
+                        msg = msgProgress.setTokens({
+                            'index': i + 1,
+                            'files': numFiles,
+                            'percent': progress,
+                            'name': caption
+                        });
+                        setTimeout(function () {
+                            $status.html(msg);
+                        }, self.processDelay);
+                    }
+                };
+
+                if (isText || isHtml) {
+                    reader.readAsText(file, self.textEncoding);
+                } else {
+                    if (isImage) {
+                        reader.readAsDataURL(file);
+                    } else {
+                        reader.readAsArrayBuffer(file);
+                    }
+                }
+                self.fileManager.add(file);
+            };
+
+            readFile(0);
+            self._updateFileDetails(numFiles, false);
+        },
+        lock: function () {
+            var self = this, $container = self.$container;
+            self._resetErrors();
+            self.disable();
+            $container.addClass('is-locked');
+            if (self.showCancel) {
+                $container.find('.fileinput-cancel').show();
+            }
+            if (self.showPause) {
+                $container.find('.fileinput-pause').show();
+            }
+            self._raise('filelock', [self.fileManager.stack, self._getExtraData()]);
+            return self.$element;
+        },
+        unlock: function (reset) {
+            var self = this, $container = self.$container;
+            if (reset === undefined) {
+                reset = true;
+            }
+            self.enable();
+            $container.removeClass('is-locked');
+            if (self.showCancel) {
+                $container.find('.fileinput-cancel').hide();
+            }
+            if (self.showPause) {
+                $container.find('.fileinput-pause').hide();
+            }
+            if (reset) {
+                self._resetFileStack();
+            }
+            self._raise('fileunlock', [self.fileManager.stack, self._getExtraData()]);
+            return self.$element;
+        },
+        resume: function () {
+            var self = this, flag = false, $pr = self.$progress, rm = self.resumableManager;
+            if (!self.enableResumableUpload) {
+                return self.$element;
+            }
+            if (self.paused) {
+                $pr.html(self.progressPauseTemplate.setTokens({
+                    percent: 101,
+                    status: self.msgUploadResume,
+                    stats: ''
+                }));
+            } else {
+                flag = true;
+            }
+            self.paused = false;
+            if (flag) {
+                $pr.html(self.progressInfoTemplate.setTokens({
+                    percent: 101,
+                    status: self.msgUploadBegin,
+                    stats: ''
+                }));
+            }
+            setTimeout(function () {
+                rm.upload();
+            }, self.processDelay);
+            return self.$element;
+        },
+        pause: function () {
+            var self = this, rm = self.resumableManager, xhr = self.ajaxRequests, len = xhr.length, i,
+                pct = rm.getProgress(), actions = self.fileActionSettings;
+            if (!self.enableResumableUpload) {
+                return self.$element;
+            }
+            if (rm.chunkIntervalId) {
+                clearInterval(rm.chunkIntervalId);
+            }
+            if (self.ajaxQueueIntervalId) {
+                clearInterval(self.ajaxQueueIntervalId);
+            }
+            self._raise('fileuploadpaused', [self.fileManager, rm]);
+            if (len > 0) {
+                for (i = 0; i < len; i += 1) {
+                    self.paused = true;
+                    xhr[i].abort();
+                }
+            }
+            if (self.showPreview) {
+                self._getThumbs().each(function () {
+                    var $thumb = $(this), fileId = $thumb.attr('data-fileid'), t = self._getLayoutTemplate('stats'),
+                        stats, $indicator = $thumb.find('.file-upload-indicator');
+                    $thumb.removeClass('file-uploading');
+                    if ($indicator.attr('title') === actions.indicatorLoadingTitle) {
+                        self._setThumbStatus($thumb, 'Paused');
+                        stats = t.setTokens({pendingTime: self.msgPaused, uploadSpeed: ''});
+                        self.paused = true;
+                        self._setProgress(pct, $thumb.find('.file-thumb-progress'), pct + '%', stats);
+                    }
+                    if (!self.fileManager.getFile(fileId)) {
+                        $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
+                    }
+                });
+            }
+            self._setProgress(101, self.$progress, self.msgPaused);
+            return self.$element;
+        },
+        cancel: function () {
+            var self = this, xhr = self.ajaxRequests, rm = self.resumableManager, len = xhr.length, i;
+            if (self.enableResumableUpload && rm.chunkIntervalId) {
+                clearInterval(rm.chunkIntervalId);
+                rm.reset();
+                self._raise('fileuploadcancelled', [self.fileManager, rm]);
+            } else {
+                self._raise('fileuploadcancelled', [self.fileManager]);
+            }
+            if (self.ajaxQueueIntervalId) {
+                clearInterval(self.ajaxQueueIntervalId);
+            }
+            self._initAjax();
+            if (len > 0) {
+                for (i = 0; i < len; i += 1) {
+                    self.cancelling = true;
+                    xhr[i].abort();
+                }
+            }
+            self._getThumbs().each(function () {
+                var $thumb = $(this), fileId = $thumb.attr('data-fileid'), $prog = $thumb.find('.file-thumb-progress');
+                $thumb.removeClass('file-uploading');
+                self._setProgress(0, $prog);
+                $prog.hide();
+                if (!self.fileManager.getFile(fileId)) {
+                    $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');
+                    $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
+                }
+                self.unlock();
+            });
+            setTimeout(function () {
+                self._setProgressCancelled();
+            }, self.processDelay);
+            return self.$element;
+        },
+        clear: function () {
+            var self = this, cap;
+            if (!self._raise('fileclear')) {
+                return;
+            }
+            self.$btnUpload.removeAttr('disabled');
+            self._getThumbs().find('video,audio,img').each(function () {
+                $h.cleanMemory($(this));
+            });
+            self._clearFileInput();
+            self._resetUpload();
+            self.clearFileStack();
+            self._resetErrors(true);
+            if (self._hasInitialPreview()) {
+                self._showFileIcon();
+                self._resetPreview();
+                self._initPreviewActions();
+                self.$container.removeClass('file-input-new');
+            } else {
+                self._getThumbs().each(function () {
+                    self._clearObjects($(this));
+                });
+                if (self.isAjaxUpload) {
+                    self.previewCache.data = {};
+                }
+                self.$preview.html('');
+                cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : '';
+                self.$caption.attr('title', '').val(cap);
+                $h.addCss(self.$container, 'file-input-new');
+                self._validateDefaultPreview();
+            }
+            if (self.$container.find($h.FRAMES).length === 0) {
+                if (!self._initCaption()) {
+                    self.$captionContainer.removeClass('icon-visible');
+                }
+            }
+            self._hideFileIcon();
+            self.$captionContainer.focus();
+            self._setFileDropZoneTitle();
+            self._raise('filecleared');
+            return self.$element;
+        },
+        reset: function () {
+            var self = this;
+            if (!self._raise('filereset')) {
+                return;
+            }
+            self.lastProgress = 0;
+            self._resetPreview();
+            self.$container.find('.fileinput-filename').text('');
+            $h.addCss(self.$container, 'file-input-new');
+            if (self.getFrames().length || self.dropZoneEnabled) {
+                self.$container.removeClass('file-input-new');
+            }
+            self.clearFileStack();
+            self._setFileDropZoneTitle();
+            return self.$element;
+        },
+        disable: function () {
+            var self = this;
+            self.isDisabled = true;
+            self._raise('filedisabled');
+            self.$element.attr('disabled', 'disabled');
+            self.$container.find('.kv-fileinput-caption').addClass('file-caption-disabled');
+            self.$container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button')
+                .attr('disabled', true);
+            $h.addCss(self.$container.find('.btn-file'), 'disabled');
+            self._initDragDrop();
+            return self.$element;
+        },
+        enable: function () {
+            var self = this;
+            self.isDisabled = false;
+            self._raise('fileenabled');
+            self.$element.removeAttr('disabled');
+            self.$container.find('.kv-fileinput-caption').removeClass('file-caption-disabled');
+            self.$container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button')
+                .removeAttr('disabled');
+            self.$container.find('.btn-file').removeClass('disabled');
+            self._initDragDrop();
+            return self.$element;
+        },
+        upload: function () {
+            var self = this, fm = self.fileManager, totLen = fm.count(), i, outData, len,
+                hasExtraData = !$.isEmptyObject(self._getExtraData());
+            if (!self.isAjaxUpload || self.isDisabled || !self._isFileSelectionValid(totLen)) {
+                return;
+            }
+            self.lastProgress = 0;
+            self._resetUpload();
+            if (totLen === 0 && !hasExtraData) {
+                self._showFileError(self.msgUploadEmpty);
+                return;
+            }
+            self.cancelling = false;
+            self.$progress.show();
+            self.lock();
+            len = fm.count();
+            if (totLen === 0 && hasExtraData) {
+                self._setProgress(2);
+                self._uploadExtraOnly();
+                return;
+            }
+            if (self.enableResumableUpload) {
+                return self.resume();
+            }
+            if (self.uploadAsync || self.enableResumableUpload) {
+                outData = self._getOutData(null);
+                self._raise('filebatchpreupload', [outData]);
+                self.fileBatchCompleted = false;
+                self.uploadCache = {content: [], config: [], tags: [], append: true};
+                for (i = 0; i < len; i++) {
+                    self.uploadCache.content[i] = null;
+                    self.uploadCache.config[i] = null;
+                    self.uploadCache.tags[i] = null;
+                }
+                self.$preview.find('.file-preview-initial').removeClass($h.SORT_CSS);
+                self._initSortable();
+                self.cacheInitialPreview = self.getPreview();
+            }
+            self._setProgress(2);
+            self.hasInitData = false;
+            if (self.uploadAsync) {
+                i = 0;
+                $.each(fm.stack, function (id) {
+                    self._uploadSingle(i, id, true);
+                    i++;
+                });
+                return;
+            }
+            self._uploadBatch();
+            return self.$element;
+        },
+        destroy: function () {
+            var self = this, $form = self.$form, $cont = self.$container, $el = self.$element, ns = self.namespace;
+            $(document).off(ns);
+            $(window).off(ns);
+            if ($form && $form.length) {
+                $form.off(ns);
+            }
+            if (self.isAjaxUpload) {
+                self._clearFileInput();
+            }
+            self._cleanup();
+            self._initPreviewCache();
+            $el.insertBefore($cont).off(ns).removeData();
+            $cont.off().remove();
+            return $el;
+        },
+        refresh: function (options) {
+            var self = this, $el = self.$element;
+            if (typeof options !== 'object' || $h.isEmpty(options)) {
+                options = self.options;
+            } else {
+                options = $.extend(true, {}, self.options, options);
+            }
+            self._init(options, true);
+            self._listen();
+            return $el;
+        },
+        zoom: function (frameId) {
+            var self = this, $frame = self._getFrame(frameId), $modal = self.$modal;
+            if (!$frame) {
+                return;
+            }
+            $h.initModal($modal);
+            $modal.html(self._getModalContent());
+            self._setZoomContent($frame);
+            $modal.modal('show');
+            self._initZoomButtons();
+        },
+        getExif: function (frameId) {
+            var self = this, $frame = self._getFrame(frameId);
+            return $frame && $frame.data('exif') || null;
+        },
+        getFrames: function (cssFilter) {
+            var self = this, $frames;
+            cssFilter = cssFilter || '';
+            $frames = self.$preview.find($h.FRAMES + cssFilter);
+            if (self.reversePreviewOrder) {
+                $frames = $($frames.get().reverse());
+            }
+            return $frames;
+        },
+        getPreview: function () {
+            var self = this;
+            return {
+                content: self.initialPreview,
+                config: self.initialPreviewConfig,
+                tags: self.initialPreviewThumbTags
+            };
+        }
+    };
+
+    $.fn.fileinput = function (option) {
+        if (!$h.hasFileAPISupport() && !$h.isIE(9)) {
+            return;
+        }
+        var args = Array.apply(null, arguments), retvals = [];
+        args.shift();
+        this.each(function () {
+            var self = $(this), data = self.data('fileinput'), options = typeof option === 'object' && option,
+                theme = options.theme || self.data('theme'), l = {}, t = {},
+                lang = options.language || self.data('language') || $.fn.fileinput.defaults.language || 'en', opt;
+            if (!data) {
+                if (theme) {
+                    t = $.fn.fileinputThemes[theme] || {};
+                }
+                if (lang !== 'en' && !$h.isEmpty($.fn.fileinputLocales[lang])) {
+                    l = $.fn.fileinputLocales[lang] || {};
+                }
+                opt = $.extend(true, {}, $.fn.fileinput.defaults, t, $.fn.fileinputLocales.zh, l, options, self.data());
+                data = new FileInput(this, opt);
+                self.data('fileinput', data);
+            }
+
+            if (typeof option === 'string') {
+                retvals.push(data[option].apply(data, args));
+            }
+        });
+        switch (retvals.length) {
+            case 0:
+                return this;
+            case 1:
+                return retvals[0];
+            default:
+                return retvals;
+        }
+    };
+
+    //noinspection HtmlUnknownAttribute
+    $.fn.fileinput.defaults = {
+        language: 'en',
+        showCaption: true,
+        showBrowse: true,
+        showPreview: true,
+        showRemove: true,
+        showUpload: true,
+        showUploadStats: true,
+        showCancel: null,
+        showPause: null,
+        showClose: true,
+        showUploadedThumbs: true,
+        browseOnZoneClick: false,
+        autoReplace: false,
+        autoOrientImage: function () { // applicable for JPEG images only and non ios safari
+            var ua = window.navigator.userAgent, webkit = !!ua.match(/WebKit/i),
+                iOS = !!ua.match(/iP(od|ad|hone)/i), iOSSafari = iOS && webkit && !ua.match(/CriOS/i);
+            return !iOSSafari;
+        },
+        autoOrientImageInitial: true,
+        required: false,
+        rtl: false,
+        hideThumbnailContent: false,
+        encodeUrl: true,
+        generateFileId: null,
+        previewClass: '',
+        captionClass: '',
+        frameClass: 'krajee-default',
+        mainClass: 'file-caption-main',
+        mainTemplate: null,
+        purifyHtml: true,
+        fileSizeGetter: null,
+        initialCaption: '',
+        initialPreview: [],
+        initialPreviewDelimiter: '*$$*',
+        initialPreviewAsData: false,
+        initialPreviewFileType: 'image',
+        initialPreviewConfig: [],
+        initialPreviewThumbTags: [],
+        previewThumbTags: {},
+        initialPreviewShowDelete: true,
+        initialPreviewDownloadUrl: '',
+        removeFromPreviewOnError: false,
+        deleteUrl: '',
+        deleteExtraData: {},
+        overwriteInitial: true,
+        sanitizeZoomCache: function (content) {
+            var $container = $(document.createElement('div')).append(content);
+            $container.find('input,select,.file-thumbnail-footer').remove();
+            return $container.html();
+        },
+        previewZoomButtonIcons: {
+            prev: '<i class="glyphicon glyphicon-triangle-left"></i>',
+            next: '<i class="glyphicon glyphicon-triangle-right"></i>',
+            toggleheader: '<i class="glyphicon glyphicon-resize-vertical"></i>',
+            fullscreen: '<i class="glyphicon glyphicon-fullscreen"></i>',
+            borderless: '<i class="glyphicon glyphicon-resize-full"></i>',
+            close: '<i class="glyphicon glyphicon-remove"></i>'
+        },
+        previewZoomButtonClasses: {
+            prev: 'btn btn-navigate',
+            next: 'btn btn-navigate',
+            toggleheader: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
+            fullscreen: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
+            borderless: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
+            close: 'btn btn-sm btn-kv btn-default btn-outline-secondary'
+        },
+        previewTemplates: {},
+        previewContentTemplates: {},
+        preferIconicPreview: false,
+        preferIconicZoomPreview: false,
+        allowedFileTypes: null,
+        allowedFileExtensions: null,
+        allowedPreviewTypes: undefined,
+        allowedPreviewMimeTypes: null,
+        allowedPreviewExtensions: null,
+        disabledPreviewTypes: undefined,
+        disabledPreviewExtensions: ['msi', 'exe', 'com', 'zip', 'rar', 'app', 'vb', 'scr'],
+        disabledPreviewMimeTypes: null,
+        defaultPreviewContent: null,
+        customLayoutTags: {},
+        customPreviewTags: {},
+        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
+        previewFileIconClass: 'file-other-icon',
+        previewFileIconSettings: {},
+        previewFileExtSettings: {},
+        buttonLabelClass: 'hidden-xs',
+        browseIcon: '<i class="glyphicon glyphicon-folder-open"></i>&nbsp;',
+        browseClass: 'btn btn-primary',
+        removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
+        removeClass: 'btn btn-default btn-secondary',
+        cancelIcon: '<i class="glyphicon glyphicon-ban-circle"></i>',
+        cancelClass: 'btn btn-default btn-secondary',
+        pauseIcon: '<i class="glyphicon glyphicon-pause"></i>',
+        pauseClass: 'btn btn-default btn-secondary',
+        uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
+        uploadClass: 'btn btn-default btn-secondary',
+        uploadUrl: null,
+        uploadUrlThumb: null,
+        uploadAsync: true,
+        uploadParamNames: {
+            chunkCount: 'chunkCount',
+            chunkIndex: 'chunkIndex',
+            chunkSize: 'chunkSize',
+            chunkSizeStart: 'chunkSizeStart',
+            chunksUploaded: 'chunksUploaded',
+            fileBlob: 'fileBlob',
+            fileId: 'fileId',
+            fileName: 'fileName',
+            fileRelativePath: 'fileRelativePath',
+            fileSize: 'fileSize',
+            retryCount: 'retryCount'
+        },
+        maxAjaxThreads: 5,
+        processDelay: 100,
+        queueDelay: 10, // must be lesser than process delay
+        progressDelay: 0, // must be lesser than process delay
+        enableResumableUpload: false,
+        resumableUploadOptions: {
+            fallback: null,
+            testUrl: null, // used for checking status of chunks/ files previously / partially uploaded
+            chunkSize: 2 * 1024, // in KB
+            maxThreads: 4,
+            maxRetries: 3,
+            showErrorLog: true
+        },
+        uploadExtraData: {},
+        zoomModalHeight: 480,
+        minImageWidth: null,
+        minImageHeight: null,
+        maxImageWidth: null,
+        maxImageHeight: null,
+        resizeImage: false,
+        resizePreference: 'width',
+        resizeQuality: 0.92,
+        resizeDefaultImageType: 'image/jpeg',
+        resizeIfSizeMoreThan: 0, // in KB
+        minFileSize: 0,
+        maxFileSize: 0,
+        maxFilePreviewSize: 25600, // 25 MB
+        minFileCount: 0,
+        maxFileCount: 0,
+        validateInitialCount: false,
+        msgValidationErrorClass: 'text-danger',
+        msgValidationErrorIcon: '<i class="glyphicon glyphicon-exclamation-sign"></i> ',
+        msgErrorClass: 'file-error-message',
+        progressThumbClass: 'progress-bar progress-bar-striped active',
+        progressClass: 'progress-bar bg-success progress-bar-success progress-bar-striped active',
+        progressInfoClass: 'progress-bar bg-info progress-bar-info progress-bar-striped active',
+        progressCompleteClass: 'progress-bar bg-success progress-bar-success',
+        progressPauseClass: 'progress-bar bg-primary progress-bar-primary progress-bar-striped active',
+        progressErrorClass: 'progress-bar bg-danger progress-bar-danger',
+        progressUploadThreshold: 99,
+        previewFileType: 'image',
+        elCaptionContainer: null,
+        elCaptionText: null,
+        elPreviewContainer: null,
+        elPreviewImage: null,
+        elPreviewStatus: null,
+        elErrorContainer: null,
+        errorCloseButton: $h.closeButton('kv-error-close'),
+        slugCallback: null,
+        dropZoneEnabled: true,
+        dropZoneTitleClass: 'file-drop-zone-title',
+        fileActionSettings: {},
+        otherActionButtons: '',
+        textEncoding: 'UTF-8',
+        ajaxSettings: {},
+        ajaxDeleteSettings: {},
+        showAjaxErrorDetails: true,
+        mergeAjaxCallbacks: false,
+        mergeAjaxDeleteCallbacks: false,
+        retryErrorUploads: true,
+        reversePreviewOrder: false,
+        usePdfRenderer: function () {
+            //noinspection JSUnresolvedVariable
+            var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
+            return !!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i) || isIE11;
+        },
+        pdfRendererUrl: '',
+        pdfRendererTemplate: '<iframe class="kv-preview-data file-preview-pdf" src="{renderer}?file={data}" {style}></iframe>'
+    };
+
+    // noinspection HtmlUnknownAttribute
+    $.fn.fileinputLocales.en = {
+        fileSingle: 'file',
+        filePlural: 'files',
+        browseLabel: 'Browse &hellip;',
+        removeLabel: 'Remove',
+        removeTitle: 'Clear all unprocessed files',
+        cancelLabel: 'Cancel',
+        cancelTitle: 'Abort ongoing upload',
+        pauseLabel: 'Pause',
+        pauseTitle: 'Pause ongoing upload',
+        uploadLabel: 'Upload',
+        uploadTitle: 'Upload selected files',
+        msgNo: 'No',
+        msgNoFilesSelected: 'No files selected',
+        msgCancelled: 'Cancelled',
+        msgPaused: 'Paused',
+        msgPlaceholder: 'Select {files}...',
+        msgZoomModalHeading: 'Detailed Preview',
+        msgFileRequired: 'You must select a file to upload.',
+        msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
+        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
+        msgFileNotFound: 'File "{name}" not found!',
+        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
+        msgFileNotReadable: 'File "{name}" is not readable.',
+        msgFilePreviewAborted: 'File preview aborted for "{name}".',
+        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
+        msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
+        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
+        msgFileTypes: {
+            'image': 'image',
+            'html': 'HTML',
+            'text': 'text',
+            'video': 'video',
+            'audio': 'audio',
+            'flash': 'flash',
+            'pdf': 'PDF',
+            'object': 'object'
+        },
+        msgUploadAborted: 'The file upload was aborted',
+        msgUploadThreshold: 'Processing...',
+        msgUploadBegin: 'Initializing...',
+        msgUploadEnd: 'Done',
+        msgUploadResume: 'Resuming upload...',
+        msgUploadEmpty: 'No valid data available for upload.',
+        msgUploadError: 'Upload Error',
+        msgDeleteError: 'Delete Error',
+        msgProgressError: 'Error',
+        msgValidationError: 'Validation Error',
+        msgLoading: 'Loading file {index} of {files} &hellip;',
+        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
+        msgSelected: '{n} {files} selected',
+        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
+        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
+        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
+        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
+        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
+        msgImageResizeError: 'Could not get the image dimensions to resize.',
+        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
+        msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+        msgAjaxProgressError: '{operation} failed',
+        msgDuplicateFile: 'File "{name}" of same size "{size} KB" has already been selected earlier. Skipping duplicate selection.',
+        msgResumableUploadRetriesExceeded: 'Upload aborted beyond <b>{max}</b> retries for file <b>{file}</b>! Error Details: <pre>{error}</pre>',
+        msgPendingTime: '{time} remaining',
+        msgCalculatingTime: 'calculating time remaining',
+        ajaxOperations: {
+            deleteThumb: 'file delete',
+            uploadThumb: 'file upload',
+            uploadBatch: 'batch file upload',
+            uploadExtra: 'form data upload'
+        },
+        dropZoneTitle: 'Drag & drop files here &hellip;',
+        dropZoneClickTitle: '<br>(or click to select {files})',
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+    
+    $.fn.fileinputLocales.zh = {
+        fileSingle: '文件',
+        filePlural: '个文件',
+        browseLabel: '选择 &hellip;',
+        removeLabel: '移除',
+        removeTitle: '清除选中文件',
+        cancelLabel: '取消',
+        cancelTitle: '取消进行中的上传',
+        pauseLabel: 'Pause',
+        pauseTitle: 'Pause ongoing upload',
+        uploadLabel: '上传',
+        uploadTitle: '上传选中文件',
+        msgNo: '没有',
+        msgNoFilesSelected: '未选择文件',
+        msgPaused: 'Paused',
+        msgCancelled: '取消',
+        msgPlaceholder: '选择 {files}...',
+        msgZoomModalHeading: '详细预览',
+        msgFileRequired: '必须选择一个文件上传.',
+        msgSizeTooSmall: '文件 "{name}" (<b>{size} KB</b>) 必须大于限定大小 <b>{minSize} KB</b>.',
+        msgSizeTooLarge: '文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.',
+        msgFilesTooLess: '你必须选择最少 <b>{n}</b> {files} 来上传. ',
+        msgFilesTooMany: '选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.',
+        msgFileNotFound: '文件 "{name}" 未找到!',
+        msgFileSecured: '安全限制,为了防止读取文件 "{name}".',
+        msgFileNotReadable: '文件 "{name}" 不可读.',
+        msgFilePreviewAborted: '取消 "{name}" 的预览.',
+        msgFilePreviewError: '读取 "{name}" 时出现了一个错误.',
+        msgInvalidFileName: '文件名 "{name}" 包含非法字符.',
+        msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.',
+        msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.',
+        msgFileTypes: {
+            'image': 'image',
+            'html': 'HTML',
+            'text': 'text',
+            'video': 'video',
+            'audio': 'audio',
+            'flash': 'flash',
+            'pdf': 'PDF',
+            'object': 'object'
+        },
+        msgUploadAborted: '该文件上传被中止',
+        msgUploadThreshold: '处理中...',
+        msgUploadBegin: '正在初始化...',
+        msgUploadEnd: '完成',
+        msgUploadResume: 'Resuming upload...',
+        msgUploadEmpty: '无效的文件上传.',
+        msgUploadError: 'Upload Error',
+        msgDeleteError: 'Delete Error',
+        msgProgressError: '上传出错',
+        msgValidationError: '验证错误',
+        msgLoading: '加载第 {index} 文件 共 {files} &hellip;',
+        msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.',
+        msgSelected: '{n} {files} 选中',
+        msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.',
+        msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.',
+        msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.',
+        msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.',
+        msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.',
+        msgImageResizeError: '无法获取的图像尺寸调整。',
+        msgImageResizeException: '调整图像大小时发生错误。<pre>{errors}</pre>',
+        msgAjaxError: '{operation} 发生错误. 请重试!',
+        msgAjaxProgressError: '{operation} 失败',
+        msgDuplicateFile: 'File "{name}" of same size "{size} KB" has already been selected earlier. Skipping duplicate selection.',
+        msgResumableUploadRetriesExceeded:  'Upload aborted beyond <b>{max}</b> retries for file <b>{file}</b>! Error Details: <pre>{error}</pre>',
+        msgPendingTime: '{time} remaining',
+        msgCalculatingTime: 'calculating time remaining',
+        ajaxOperations: {
+            deleteThumb: '删除文件',
+            uploadThumb: '上传文件',
+            uploadBatch: '批量上传',
+            uploadExtra: '表单数据上传'
+        },
+        dropZoneTitle: '拖拽文件到这里 &hellip;<br>支持多文件同时上传',
+        dropZoneClickTitle: '<br>(或点击{files}按钮选择文件)',
+        fileActionSettings: {
+            removeTitle: '删除文件',
+            uploadTitle: '上传文件',
+            downloadTitle: '下载文件',
+            uploadRetryTitle: '重试',
+            zoomTitle: '查看详情',
+            dragTitle: '移动 / 重置',
+            indicatorNewTitle: '没有上传',
+            indicatorSuccessTitle: '上传',
+            indicatorErrorTitle: '上传错误',
+            indicatorPausedTitle: 'Upload Paused',
+            indicatorLoadingTitle:  '上传 ...'
+        },
+        previewZoomButtonTitles: {
+            prev: '预览上一个文件',
+            next: '预览下一个文件',
+            toggleheader: '缩放',
+            fullscreen: '全屏',
+            borderless: '无边界模式',
+            close: '关闭当前预览'
+        }
+    };
+
+    $.fn.fileinput.Constructor = FileInput;
+
+    /**
+     * Convert automatically file inputs with class 'file' into a bootstrap fileinput control.
+     */
+    $(document).ready(function () {
+        var $input = $('input.file[type=file]');
+        if ($input.length) {
+            $input.fileinput();
+        }
+    });
+}));
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css
new file mode 100644
index 0000000..2b2042c
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css
@@ -0,0 +1,12 @@
+/*!
+ * bootstrap-fileinput v5.0.4
+ * http://plugins.krajee.com/file-input
+ *
+ * Krajee default styling for bootstrap-fileinput.
+ *
+ * Author: Kartik Visweswaran
+ * Copyright: 2014 - 2019, Kartik Visweswaran, Krajee.com
+ *
+ * Licensed under the BSD-3-Clause
+ * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
+ */.btn-file input[type=file],.file-caption-icon,.file-no-browse,.file-preview .fileinput-remove,.file-zoom-dialog .btn-navigate,.file-zoom-dialog .floating-buttons,.krajee-default .file-thumb-progress{position:absolute}.file-loading input[type=file],input[type=file].file-loading{width:0;height:0}.file-no-browse{left:50%;bottom:20%;width:1px;height:1px;font-size:0;opacity:0;border:none;background:0 0;outline:0;box-shadow:none}.file-caption-icon,.file-input-ajax-new .fileinput-remove-button,.file-input-ajax-new .fileinput-upload-button,.file-input-ajax-new .no-browse .input-group-btn,.file-input-new .close,.file-input-new .file-preview,.file-input-new .fileinput-remove-button,.file-input-new .fileinput-upload-button,.file-input-new .glyphicon-file,.file-input-new .no-browse .input-group-btn,.file-zoom-dialog .modal-header:after,.file-zoom-dialog .modal-header:before,.hide-content .kv-file-content,.is-locked .fileinput-remove-button,.is-locked .fileinput-upload-button,.kv-hidden{display:none}.file-caption-icon .kv-caption-icon{line-height:inherit}.btn-file,.file-caption,.file-input,.file-loading:before,.file-preview,.file-zoom-dialog .modal-dialog,.krajee-default .file-thumbnail-footer,.krajee-default.file-preview-frame{position:relative}.file-error-message pre,.file-error-message ul,.krajee-default .file-actions,.krajee-default .file-other-error{text-align:left}.file-error-message pre,.file-error-message ul{margin:0}.krajee-default .file-drag-handle,.krajee-default .file-upload-indicator{float:left;margin-top:10px;width:16px;height:16px}.krajee-default .file-thumb-progress .progress,.krajee-default .file-thumb-progress .progress-bar{height:20px;font-family:Verdana,Helvetica,sans-serif;font-size:9px}.krajee-default .file-thumb-progress .progress,.kv-upload-progress .progress{background-color:#ccc}.krajee-default .file-caption-info,.krajee-default .file-size-info{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:160px;height:15px;margin:auto}.file-zoom-content>.file-object.type-flash,.file-zoom-content>.file-object.type-image,.file-zoom-content>.file-object.type-video{max-width:100%;max-height:100%;width:auto}.file-zoom-content>.file-object.type-flash,.file-zoom-content>.file-object.type-video{height:100%}.file-zoom-content>.file-object.type-default,.file-zoom-content>.file-object.type-html,.file-zoom-content>.file-object.type-pdf,.file-zoom-content>.file-object.type-text{width:100%}.file-loading:before{content:" Loading...";display:inline-block;padding-left:20px;line-height:16px;font-size:13px;font-variant:small-caps;color:#999;background:url(loading.gif) top left no-repeat}.file-object{margin:0 0 -5px;padding:0}.btn-file{overflow:hidden}.btn-file input[type=file]{top:0;left:0;min-width:100%;min-height:100%;text-align:right;opacity:0;background:none;cursor:inherit;display:block}.btn-file ::-ms-browse{font-size:10000px;width:100%;height:100%}.file-caption .file-caption-name{width:100%;margin:0;padding:0;box-shadow:none;border:none;background:0 0;outline:0}.file-caption.icon-visible .file-caption-icon{display:inline-block}.file-caption.icon-visible .file-caption-name{padding-left:15px}.file-caption-icon{left:8px}.file-error-message{color:#a94442;background-color:#f2dede;margin:5px;border:1px solid #ebccd1;border-radius:4px;padding:15px}.file-error-message pre{margin:5px 0}.file-caption-disabled{background-color:#eee;cursor:not-allowed;opacity:1}.file-preview{border-radius:5px;border:1px solid #ddd;padding:8px;width:100%;margin-bottom:5px}.file-preview .btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.file-preview .fileinput-remove{top:1px;right:1px;line-height:10px}.file-preview .clickable{cursor:pointer}.file-preview-image{font:40px Impact,Charcoal,sans-serif;color:green}.krajee-default.file-preview-frame{margin:8px;border:1px solid rgba(0,0,0,.2);box-shadow:0 0 10px 0 rgba(0,0,0,.2);padding:6px;float:left;text-align:center}.krajee-default.file-preview-frame .kv-file-content{width:213px;height:160px}.krajee-default .file-preview-other-frame{display:flex;align-items:center;justify-content:center}.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered{width:400px}.krajee-default.file-preview-frame[data-template=audio] .kv-file-content{width:240px;height:55px}.krajee-default.file-preview-frame .file-thumbnail-footer{height:70px}.krajee-default.file-preview-frame:not(.file-preview-error):hover{border:1px solid rgba(0,0,0,.3);box-shadow:0 0 10px 0 rgba(0,0,0,.4)}.krajee-default .file-preview-text{display:block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.krajee-default .file-preview-html{border:1px solid #ddd;padding:8px;overflow:auto}.krajee-default .file-other-icon{font-size:6em;line-height:1}.krajee-default .file-footer-buttons{float:right}.krajee-default .file-footer-caption{display:block;text-align:center;padding-top:4px;font-size:11px;color:#777;margin-bottom:30px}.file-upload-stats{font-size:10px;text-align:center;width:100%}.kv-upload-progress .file-upload-stats{font-size:12px;margin:-10px 0 5px}.krajee-default .file-preview-error{opacity:.65;box-shadow:none}.krajee-default .file-thumb-progress{height:11px;top:37px;left:0;right:0}.krajee-default.kvsortable-ghost{background:#e1edf7;border:2px solid #a1abff}.krajee-default .file-preview-other:hover{opacity:.8}.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover{color:#000}.kv-upload-progress .progress{height:20px;margin:10px 0;overflow:hidden}.kv-upload-progress .progress-bar{height:20px;font-family:Verdana,Helvetica,sans-serif}.file-zoom-dialog .file-other-icon{font-size:22em;font-size:50vmin}.file-zoom-dialog .modal-dialog{width:auto}.file-zoom-dialog .modal-header{display:flex;align-items:center;justify-content:space-between}.file-zoom-dialog .btn-navigate{padding:0;margin:0;background:0 0;text-decoration:none;outline:0;opacity:.7;top:45%;font-size:4em;color:#1c94c4}.file-zoom-dialog .btn-navigate:not([disabled]):hover{outline:0;box-shadow:none;opacity:.6}.file-zoom-dialog .floating-buttons{top:5px;right:10px}.file-zoom-dialog .btn-navigate[disabled]{opacity:.3}.file-zoom-dialog .btn-prev{left:1px}.file-zoom-dialog .btn-next{right:1px}.file-zoom-dialog .kv-zoom-title{font-weight:300;color:#999;max-width:50%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-input-ajax-new .no-browse .form-control,.file-input-new .no-browse .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.file-caption-main{width:100%}.file-thumb-loading{background:url(loading.gif) center center no-repeat content-box!important}.file-drop-zone{border:1px dashed #aaa;border-radius:4px;text-align:center;vertical-align:middle;margin:12px 15px 12px 12px;padding:5px}.file-drop-zone.clickable:hover{border:2px dashed #999}.file-drop-zone.clickable:focus{border:2px solid #5acde2}.file-drop-zone .file-preview-thumbnails{cursor:default}.file-drop-zone-title{color:#aaa;font-size:1.6em;padding:85px 10px;cursor:default}.file-highlighted{border:2px dashed #999!important;background-color:#eee}.file-uploading{background:url(loading-sm.gif) center bottom 10px no-repeat;opacity:.65}.file-zoom-fullscreen .modal-dialog{min-width:100%;margin:0}.file-zoom-fullscreen .modal-content{border-radius:0;box-shadow:none;min-height:100vh}.file-zoom-fullscreen .modal-body{overflow-y:auto}.floating-buttons{z-index:3000}.floating-buttons .btn-kv{margin-left:3px;z-index:3000}.kv-zoom-actions .btn-kv{margin-left:3px}.file-zoom-content{height:480px;text-align:center}.file-zoom-content .file-preview-image,.file-zoom-content .file-preview-video{max-height:100%}.file-zoom-content>.file-object.type-image{height:auto;min-height:inherit}.file-zoom-content>.file-object.type-audio{width:auto;height:30px}@media (min-width:576px){.file-zoom-dialog .modal-dialog{max-width:500px}}@media (min-width:992px){.file-zoom-dialog .modal-lg{max-width:800px}}@media (max-width:767px){.file-preview-thumbnails{display:flex;justify-content:center;align-items:center;flex-direction:column}.file-zoom-dialog .modal-header{flex-direction:column}}@media (max-width:350px){.krajee-default.file-preview-frame:not([data-template=audio]) .kv-file-content{width:160px}}@media (max-width:420px){.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered{width:100%}}.file-loading[dir=rtl]:before{background:url(loading.gif) top right no-repeat;padding-left:0;padding-right:20px}.file-sortable .file-drag-handle{cursor:move;opacity:1}.file-sortable .file-drag-handle:hover{opacity:.7}.clickable .file-drop-zone-title{cursor:pointer}.file-preview-initial.sortable-chosen{background-color:#d9edf7}
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js
new file mode 100644
index 0000000..c627c6c
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js
@@ -0,0 +1,10 @@
+/*!
+  bootstrap-fileinput v5.0.4
+  http://plugins.krajee.com/file-input
+ 
+  Author: Kartik Visweswaran
+  Copyright: 2014 - 2019, Kartik Visweswaran, Krajee.com
+ 
+  Licensed under the BSD-3-Clause
+  https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
+ */(function(factory){'use strict';if(typeof define==='function'&&define.amd){define(['jquery'],factory);}else{if(typeof module==='object'&&module.exports){module.exports=factory(require('jquery'));}else{factory(window.jQuery);}}}(function($){'use strict';$.fn.fileinputLocales={};$.fn.fileinputThemes={};String.prototype.setTokens=function(replacePairs){var str=this.toString(),key,re;for(key in replacePairs){if(replacePairs.hasOwnProperty(key)){re=new RegExp('\{'+key+'\}','g');str=str.replace(re,replacePairs[key]);}}return str;};var $h,FileInput;$h={FRAMES:'.kv-preview-thumb',SORT_CSS:'file-sortable',OBJECT_PARAMS:'<param name="controller" value="true" />\n'+'<param name="allowFullScreen" value="true" />\n'+'<param name="allowScriptAccess" value="always" />\n'+'<param name="autoPlay" value="false" />\n'+'<param name="autoStart" value="false" />\n'+'<param name="quality" value="high" />\n',DEFAULT_PREVIEW:'<div class="file-preview-other">\n'+'<span class="{previewFileIconClass}">{previewFileIcon}</span>\n'+'</div>',MODAL_ID:'kvFileinputModal',MODAL_EVENTS:['show','shown','hide','hidden','loaded'],logMessages:{ajaxError:'{status}: {error}. Error Details: {text}.',badDroppedFiles:'Error scanning dropped files!',badExifParser:'Error loading the piexif.js library. {details}',badInputType:'The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.',exifWarning:'To avoid this warning, either set "autoOrientImage" to "false" OR ensure you have loaded '+'the "piexif.js" library correctly on your page before the "fileinput.js" script.',invalidChunkSize:'Invalid upload chunk size: "{chunkSize}". Resumable uploads are disabled.',invalidThumb:'Invalid thumb frame with id: "{id}".',noResumableSupport:'The browser does not support resumable or chunk uploads.',noUploadUrl:'The "uploadUrl" is not set. Ajax uploads and resumable uploads have been disabled.',retryStatus:'Retrying upload for chunk # {chunk} for {filename}... retry # {retry}.'},objUrl:window.URL||window.webkitURL,now:function(){return new Date();},round:function(num){num=parseFloat(num);return isNaN(num)?0:Math.floor(Math.round(num));},getFileRelativePath:function(file){return String(file.relativePath||file.webkitRelativePath||$h.getFileName(file)||null);},getFileId:function(file,generateFileId){var relativePath=$h.getFileRelativePath(file);if(typeof generateFileId==='function'){return generateFileId(file);}if(!file){return null;}if(!relativePath){return null;}return(file.size+'_'+relativePath.replace(/\s/img,'_'));},getElapsed:function(seconds){var delta=seconds,out='',result={},structure={year:31536000,month:2592000,week:604800,day:86400,hour:3600,minute:60,second:1};Object.keys(structure).forEach(function(key){result[key]=Math.floor(delta/structure[key]);delta-=result[key]*structure[key];});$.each(result,function(key,value){if(value>0){out+=(out?' ':'')+value+key.substring(0,1);}});return out;},debounce:function(func,delay){var inDebounce;return function(){var args=arguments,context=this;clearTimeout(inDebounce);inDebounce=setTimeout(function(){func.apply(context,args);},delay);};},stopEvent:function(e){e.stopPropagation();e.preventDefault();},getFileName:function(file){return file?(file.fileName||file.name||''):'';},createObjectURL:function(data){if($h.objUrl&&$h.objUrl.createObjectURL&&data){return $h.objUrl.createObjectURL(data);}return'';},revokeObjectURL:function(data){if($h.objUrl&&$h.objUrl.revokeObjectURL&&data){$h.objUrl.revokeObjectURL(data);}},compare:function(input,str,exact){return input!==undefined&&(exact?input===str:input.match(str));},isIE:function(ver){var div,status;if(navigator.appName!=='Microsoft Internet Explorer'){return false;}if(ver===10){return new RegExp('msie\\s'+ver,'i').test(navigator.userAgent);}div=document.createElement('div');div.innerHTML='<!--[if IE '+ver+']> <i></i> <![endif]-->';status=div.getElementsByTagName('i').length;document.body.appendChild(div);div.parentNode.removeChild(div);return status;},canAssignFilesToInput:function(){var input=document.createElement('input');try{input.type='file';input.files=null;return true;}catch(err){return false;}},getDragDropFolders:function(items){var i,item,len=items?items.length:0,folders=0;if(len>0&&items[0].webkitGetAsEntry()){for(i=0;i<len;i++){item=items[i].webkitGetAsEntry();if(item&&item.isDirectory){folders++;}}}return folders;},initModal:function($modal){var $body=$('body');if($body.length){$modal.appendTo($body);}},isEmpty:function(value,trim){return value===undefined||value===null||value.length===0||(trim&&$.trim(value)==='');},isArray:function(a){return Array.isArray(a)||Object.prototype.toString.call(a)==='[object Array]';},ifSet:function(needle,haystack,def){def=def||'';return(haystack&&typeof haystack==='object'&&needle in haystack)?haystack[needle]:def;},cleanArray:function(arr){if(!(arr instanceof Array)){arr=[];}return arr.filter(function(e){return(e!==undefined&&e!==null);});},spliceArray:function(arr,index,reverseOrder){var i,j=0,out=[],newArr;if(!(arr instanceof Array)){return[];}newArr=$.extend(true,[],arr);if(reverseOrder){newArr.reverse();}for(i=0;i<newArr.length;i++){if(i!==index){out[j]=newArr[i];j++;}}if(reverseOrder){out.reverse();}return out;},getNum:function(num,def){def=def||0;if(typeof num==='number'){return num;}if(typeof num==='string'){num=parseFloat(num);}return isNaN(num)?def:num;},hasFileAPISupport:function(){return!!(window.File&&window.FileReader);},hasDragDropSupport:function(){var div=document.createElement('div');return!$h.isIE(9)&&(div.draggable!==undefined||(div.ondragstart!==undefined&&div.ondrop!==undefined));},hasFileUploadSupport:function(){return $h.hasFileAPISupport()&&window.FormData;},hasBlobSupport:function(){try{return!!window.Blob&&Boolean(new Blob());}catch(e){return false;}},hasArrayBufferViewSupport:function(){try{return new Blob([new Uint8Array(100)]).size===100;}catch(e){return false;}},hasResumableUploadSupport:function(){return $h.hasFileUploadSupport()&&$h.hasBlobSupport()&&$h.hasArrayBufferViewSupport()&&(!!Blob.prototype.webkitSlice||!!Blob.prototype.mozSlice||!!Blob.prototype.slice||false);},dataURI2Blob:function(dataURI){var BlobBuilder=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,canBlob=$h.hasBlobSupport(),byteStr,arrayBuffer,intArray,i,mimeStr,bb,canProceed=(canBlob||BlobBuilder)&&window.atob&&window.ArrayBuffer&&window.Uint8Array;if(!canProceed){return null;}if(dataURI.split(',')[0].indexOf('base64')>=0){byteStr=atob(dataURI.split(',')[1]);}else{byteStr=decodeURIComponent(dataURI.split(',')[1]);}arrayBuffer=new ArrayBuffer(byteStr.length);intArray=new Uint8Array(arrayBuffer);for(i=0;i<byteStr.length;i+=1){intArray[i]=byteStr.charCodeAt(i);}mimeStr=dataURI.split(',')[0].split(':')[1].split(';')[0];if(canBlob){return new Blob([$h.hasArrayBufferViewSupport()?intArray:arrayBuffer],{type:mimeStr});}bb=new BlobBuilder();bb.append(arrayBuffer);return bb.getBlob(mimeStr);},arrayBuffer2String:function(buffer){if(window.TextDecoder){return new TextDecoder('utf-8').decode(buffer);}var array=Array.prototype.slice.apply(new Uint8Array(buffer)),out='',i=0,len,c,char2,char3;len=array.length;while(i<len){c=array[i++];switch(c>>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:out+=String.fromCharCode(c);break;case 12:case 13:char2=array[i++];out+=String.fromCharCode(((c&0x1F)<<6)|(char2&0x3F));break;case 14:char2=array[i++];char3=array[i++];out+=String.fromCharCode(((c&0x0F)<<12)|((char2&0x3F)<<6)|((char3&0x3F)<<0));break;}}return out;},isHtml:function(str){var a=document.createElement('div');a.innerHTML=str;for(var c=a.childNodes,i=c.length;i--;){if(c[i].nodeType===1){return true;}}return false;},isSvg:function(str){return str.match(/^\s*<\?xml/i)&&(str.match(/<!DOCTYPE svg/i)||str.match(/<svg/i));},getMimeType:function(signature,contents,type){switch(signature){case'ffd8ffe0':case'ffd8ffe1':case'ffd8ffe2':return'image/jpeg';case'89504E47':return'image/png';case'47494638':return'image/gif';case'49492a00':return'image/tiff';case'52494646':return'image/webp';case'66747970':return'video/3gp';case'4f676753':return'video/ogg';case'1a45dfa3':return'video/mkv';case'000001ba':case'000001b3':return'video/mpeg';case'3026b275':return'video/wmv';case'25504446':return'application/pdf';case'25215053':return'application/ps';case'504b0304':case'504b0506':case'504b0508':return'application/zip';case'377abcaf':return'application/7z';case'75737461':return'application/tar';case'7801730d':return'application/dmg';default:switch(signature.substring(0,6)){case'435753':return'application/x-shockwave-flash';case'494433':return'audio/mp3';case'425a68':return'application/bzip';default:switch(signature.substring(0,4)){case'424d':return'image/bmp';case'fffb':return'audio/mp3';case'4d5a':return'application/exe';case'1f9d':case'1fa0':return'application/zip';case'1f8b':return'application/gzip';default:return contents&&!contents.match(/[^\u0000-\u007f]/)?'application/text-plain':type;}}}},addCss:function($el,css){$el.removeClass(css).addClass(css);},getElement:function(options,param,value){return($h.isEmpty(options)||$h.isEmpty(options[param]))?value:$(options[param]);},uniqId:function(){return Math.round(new Date().getTime())+'_'+Math.round(Math.random()*100);},htmlEncode:function(str,undefVal){if(str===undefined){return undefVal||null;}return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&apos;');},replaceTags:function(str,tags){var out=str;if(!tags){return out;}$.each(tags,function(key,value){if(typeof value==='function'){value=value();}out=out.split(key).join(value);});return out;},cleanMemory:function($thumb){var data=$thumb.is('img')?$thumb.attr('src'):$thumb.find('source').attr('src');$h.revokeObjectURL(data);},findFileName:function(filePath){var sepIndex=filePath.lastIndexOf('/');if(sepIndex===-1){sepIndex=filePath.lastIndexOf('\\');}return filePath.split(filePath.substring(sepIndex,sepIndex+1)).pop();},checkFullScreen:function(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement;},toggleFullScreen:function(maximize){var doc=document,de=doc.documentElement;if(de&&maximize&&!$h.checkFullScreen()){if(de.requestFullscreen){de.requestFullscreen();}else{if(de.msRequestFullscreen){de.msRequestFullscreen();}else{if(de.mozRequestFullScreen){de.mozRequestFullScreen();}else{if(de.webkitRequestFullscreen){de.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);}}}}}else{if(doc.exitFullscreen){doc.exitFullscreen();}else{if(doc.msExitFullscreen){doc.msExitFullscreen();}else{if(doc.mozCancelFullScreen){doc.mozCancelFullScreen();}else{if(doc.webkitExitFullscreen){doc.webkitExitFullscreen();}}}}}},moveArray:function(arr,oldIndex,newIndex,reverseOrder){var newArr=$.extend(true,[],arr);if(reverseOrder){newArr.reverse();}if(newIndex>=newArr.length){var k=newIndex-newArr.length;while((k--)+1){newArr.push(undefined);}}newArr.splice(newIndex,0,newArr.splice(oldIndex,1)[0]);if(reverseOrder){newArr.reverse();}return newArr;},cleanZoomCache:function($el){var $cache=$el.closest('.kv-zoom-cache-theme');if(!$cache.length){$cache=$el.closest('.kv-zoom-cache');}$cache.remove();},closeButton:function(css){css=css?'close '+css:'close';return'<button type="button" class="'+css+'" aria-label="Close">\n'+'  <span aria-hidden="true">&times;</span>\n'+'</button>';},getRotation:function(value){switch(value){case 2:return'rotateY(180deg)';case 3:return'rotate(180deg)';case 4:return'rotate(180deg) rotateY(180deg)';case 5:return'rotate(270deg) rotateY(180deg)';case 6:return'rotate(90deg)';case 7:return'rotate(90deg) rotateY(180deg)';case 8:return'rotate(270deg)';default:return'';}},setTransform:function(el,val){if(!el){return;}el.style.transform=val;el.style.webkitTransform=val;el.style['-moz-transform']=val;el.style['-ms-transform']=val;el.style['-o-transform']=val;}};FileInput=function(element,options){var self=this;self.$element=$(element);self.$parent=self.$element.parent();if(!self._validate()){return;}self.isPreviewable=$h.hasFileAPISupport();self.isIE9=$h.isIE(9);self.isIE10=$h.isIE(10);if(self.isPreviewable||self.isIE9){self._init(options);self._listen();}self.$element.removeClass('file-loading');};FileInput.prototype={constructor:FileInput,_cleanup:function(){var self=this;self.reader=null;self.clearFileStack();self.fileBatchCompleted=true;self.isError=false;self.cancelling=false;self.paused=false;self.lastProgress=0;self._initAjax();},_initAjax:function(){var self=this;self.ajaxQueue=[];self.ajaxRequests=[];self.ajaxQueueIntervalId=null;self.ajaxCurrentThreads=0;self.ajaxAborted=false;},_init:function(options,refreshMode){var self=this,f,$el=self.$element,$cont,t,tmp;self.options=options;$.each(options,function(key,value){switch(key){case'minFileCount':case'maxFileCount':case'minFileSize':case'maxFileSize':case'maxFilePreviewSize':case'resizeImageQuality':case'resizeIfSizeMoreThan':case'progressUploadThreshold':case'initialPreviewCount':case'zoomModalHeight':case'minImageHeight':case'maxImageHeight':case'minImageWidth':case'maxImageWidth':self[key]=$h.getNum(value);break;default:self[key]=value;break;}});if(self.rtl){tmp=self.previewZoomButtonIcons.prev;self.previewZoomButtonIcons.prev=self.previewZoomButtonIcons.next;self.previewZoomButtonIcons.next=tmp;}if(!isNaN(self.maxAjaxThreads)&&self.maxAjaxThreads<self.resumableUploadOptions.maxThreads){self.resumableUploadOptions.maxThreads=self.maxAjaxThreads;}self._initFileManager();if(typeof self.autoOrientImage==='function'){self.autoOrientImage=self.autoOrientImage();}if(typeof self.autoOrientImageInitial==='function'){self.autoOrientImageInitial=self.autoOrientImageInitial();}if(!refreshMode){self._cleanup();}self.$form=$el.closest('form');self._initTemplateDefaults();self.uploadFileAttr=!$h.isEmpty($el.attr('name'))?$el.attr('name'):'file_data';t=self._getLayoutTemplate('progress');self.progressTemplate=t.replace('{class}',self.progressClass);self.progressInfoTemplate=t.replace('{class}',self.progressInfoClass);self.progressPauseTemplate=t.replace('{class}',self.progressPauseClass);self.progressCompleteTemplate=t.replace('{class}',self.progressCompleteClass);self.progressErrorTemplate=t.replace('{class}',self.progressErrorClass);self.isDisabled=$el.attr('disabled')||$el.attr('readonly');if(self.isDisabled){$el.attr('disabled',true);}self.isClickable=self.browseOnZoneClick&&self.showPreview&&(self.dropZoneEnabled||!$h.isEmpty(self.defaultPreviewContent));self.isAjaxUpload=$h.hasFileUploadSupport()&&!$h.isEmpty(self.uploadUrl);self.dropZoneEnabled=$h.hasDragDropSupport()&&self.dropZoneEnabled;if(!self.isAjaxUpload){self.dropZoneEnabled=self.dropZoneEnabled&&$h.canAssignFilesToInput();}self.slug=typeof options.slugCallback==='function'?options.slugCallback:self._slugDefault;self.mainTemplate=self.showCaption?self._getLayoutTemplate('main1'):self._getLayoutTemplate('main2');self.captionTemplate=self._getLayoutTemplate('caption');self.previewGenericTemplate=self._getPreviewTemplate('generic');if(!self.imageCanvas&&self.resizeImage&&(self.maxImageWidth||self.maxImageHeight)){self.imageCanvas=document.createElement('canvas');self.imageCanvasContext=self.imageCanvas.getContext('2d');}if($h.isEmpty($el.attr('id'))){$el.attr('id',$h.uniqId());}self.namespace='.fileinput_'+$el.attr('id').replace(/-/g,'_');if(self.$container===undefined){self.$container=self._createContainer();}else{self._refreshContainer();}$cont=self.$container;self.$dropZone=$cont.find('.file-drop-zone');self.$progress=$cont.find('.kv-upload-progress');self.$btnUpload=$cont.find('.fileinput-upload');self.$captionContainer=$h.getElement(options,'elCaptionContainer',$cont.find('.file-caption'));self.$caption=$h.getElement(options,'elCaptionText',$cont.find('.file-caption-name'));if(!$h.isEmpty(self.msgPlaceholder)){f=$el.attr('multiple')?self.filePlural:self.fileSingle;self.$caption.attr('placeholder',self.msgPlaceholder.replace('{files}',f));}self.$captionIcon=self.$captionContainer.find('.file-caption-icon');self.$previewContainer=$h.getElement(options,'elPreviewContainer',$cont.find('.file-preview'));self.$preview=$h.getElement(options,'elPreviewImage',$cont.find('.file-preview-thumbnails'));self.$previewStatus=$h.getElement(options,'elPreviewStatus',$cont.find('.file-preview-status'));self.$errorContainer=$h.getElement(options,'elErrorContainer',self.$previewContainer.find('.kv-fileinput-error'));self._validateDisabled();if(!$h.isEmpty(self.msgErrorClass)){$h.addCss(self.$errorContainer,self.msgErrorClass);}if(!refreshMode){self.$errorContainer.hide();self.previewInitId='preview-'+$h.uniqId();self._initPreviewCache();self._initPreview(true);self._initPreviewActions();if(self.$parent.hasClass('file-loading')){self.$container.insertBefore(self.$parent);self.$parent.remove();}}else{if(!self._errorsExist()){self.$errorContainer.hide();}}self._setFileDropZoneTitle();if($el.attr('disabled')){self.disable();}self._initZoom();if(self.hideThumbnailContent){$h.addCss(self.$preview,'hide-content');}},_initFileManager:function(){var self=this;self.fileManager={stack:{},processed:[],errors:[],loadedImages:{},totalImages:0,totalFiles:null,totalSize:null,uploadedSize:0,stats:{},initStats:function(id){var data={started:$h.now().getTime()};if(id){self.fileManager.stats[id]=data;}else{self.fileManager.stats=data;}},getUploadStats:function(id,loaded,total){var fm=self.fileManager,started=id?fm.stats[id]&&fm.stats[id].started||null:null;if(!started){started=$h.now().getTime();}var elapsed=($h.now().getTime()-started)/1000,speeds=['B/s','KB/s','MB/s','GB/s','TB/s','PB/s','EB/s','ZB/s','YB/s'],bps=elapsed?loaded/elapsed:0,bitrate=self._getSize(bps,speeds),pendingBytes=total-loaded,out={fileId:id,started:started,elapsed:elapsed,loaded:loaded,total:total,bps:bps,bitrate:bitrate,pendingBytes:pendingBytes};if(id){fm.stats[id]=out;}else{fm.stats=out;}return out;},exists:function(id){return $.inArray(id,self.fileManager.getIdList())!==-1;},count:function(){return self.fileManager.getIdList().length;},total:function(){var fm=self.fileManager;if(!fm.totalFiles){fm.totalFiles=fm.count();}return fm.totalFiles;},getTotalSize:function(){var fm=self.fileManager;if(fm.totalSize){return fm.totalSize;}fm.totalSize=0;$.each(self.fileManager.stack,function(id,f){var size=parseFloat(f.size);fm.totalSize+=isNaN(size)?0:size;});return fm.totalSize;},add:function(file,id){if(!id){id=self.fileManager.getId(file);}if(!id){return;}self.fileManager.stack[id]={file:file,name:$h.getFileName(file),relativePath:$h.getFileRelativePath(file),size:file.size,nameFmt:self._getFileName(file,''),sizeFmt:self._getSize(file.size)};},remove:function($thumb){var id=$thumb.attr('data-fileid');if(id){self.fileManager.removeFile(id);}},removeFile:function(id){delete self.fileManager.stack[id];delete self.fileManager.loadedImages[id];},move:function(idFrom,idTo){var result={},stack=self.fileManager.stack;if(!idFrom&&!idTo||idFrom===idTo){return;}$.each(stack,function(k,v){if(k!==idFrom){result[k]=v;}if(k===idTo){result[idFrom]=stack[idFrom];}});self.fileManager.stack=result;},list:function(){var files=[];$.each(self.fileManager.stack,function(k,v){if(v&&v.file){files.push(v.file);}});return files;},isPending:function(id){return $.inArray(id,self.fileManager.processed)===-1&&self.fileManager.exists(id);},isProcessed:function(){var processed=true,fm=self.fileManager;$.each(fm.stack,function(id){if(fm.isPending(id)){processed=false;}});return processed;},clear:function(){var fm=self.fileManager;fm.totalFiles=null;fm.totalSize=null;fm.uploadedSize=0;fm.stack={};fm.errors=[];fm.processed=[];fm.stats={};fm.clearImages();},clearImages:function(){self.fileManager.loadedImages={};self.fileManager.totalImages=0;},addImage:function(id,config){self.fileManager.loadedImages[id]=config;},removeImage:function(id){delete self.fileManager.loadedImages[id];},getImageIdList:function(){return Object.keys(self.fileManager.loadedImages);},getImageCount:function(){return self.fileManager.getImageIdList().length;},getId:function(file){return self._getFileId(file);},getIndex:function(id){return self.fileManager.getIdList().indexOf(id);},getThumb:function(id){var $thumb=null;self._getThumbs().each(function(){if($(this).attr('data-fileid')===id){$thumb=$(this);}});return $thumb;},getThumbIndex:function($thumb){var id=$thumb.attr('data-fileid');return self.fileManager.getIndex(id);},getIdList:function(){return Object.keys(self.fileManager.stack);},getFile:function(id){return self.fileManager.stack[id]||null;},getFileName:function(id,fmt){var file=self.fileManager.getFile(id);if(!file){return'';}return fmt?(file.nameFmt||''):file.name||'';},getFirstFile:function(){var ids=self.fileManager.getIdList(),id=ids&&ids.length?ids[0]:null;return self.fileManager.getFile(id);},setFile:function(id,file){if(self.fileManager.getFile(id)){self.fileManager.stack[id].file=file;}else{self.fileManager.add(file,id);}},setProcessed:function(id){self.fileManager.processed.push(id);},getProgress:function(){var total=self.fileManager.total(),processed=self.fileManager.processed.length;if(!total){return 0;}return Math.ceil(processed/total*100);},setProgress:function(id,pct){var f=self.fileManager.getFile(id);if(!isNaN(pct)&&f){f.progress=pct;}}};},_setUploadData:function(fd,config){var self=this;$.each(config,function(key,value){var param=self.uploadParamNames[key]||key;if($h.isArray(value)){fd.append(param,value[0],value[1]);}else{fd.append(param,value);}});},_initResumableUpload:function(){var self=this,opts=self.resumableUploadOptions,logs=$h.logMessages;if(!self.enableResumableUpload){return;}if(opts.fallback!==false&&typeof opts.fallback!=='function'){opts.fallback=function(s){s._log(logs.noResumableSupport);s.enableResumableUpload=false;};}if(!$h.hasResumableUploadSupport()&&opts.fallback!==false){opts.fallback(self);return;}if(!self.uploadUrl&&self.enableResumableUpload){self._log(logs.noUploadUrl);self.enableResumableUpload=false;return;}opts.chunkSize=parseFloat(opts.chunkSize);if(opts.chunkSize<=0||isNaN(opts.chunkSize)){self._log(logs.invalidChunkSize,{chunkSize:opts.chunkSize});self.enableResumableUpload=false;return;}self.resumableManager={init:function(id,f,index){var rm=self.resumableManager,fm=self.fileManager;rm.currThreads=0;rm.logs=[];rm.stack=[];rm.error='';rm.chunkIntervalId=null;rm.id=id;rm.file=f.file;rm.fileName=f.name;rm.fileIndex=index;rm.completed=false;rm.testing=false;rm.lastProgress=0;if(self.showPreview){rm.$thumb=fm.getThumb(id)||null;rm.$progress=rm.$btnDelete=null;if(rm.$thumb&&rm.$thumb.length){rm.$progress=rm.$thumb.find('.file-thumb-progress');rm.$btnDelete=rm.$thumb.find('.kv-file-remove');}}rm.chunkSize=self.resumableUploadOptions.chunkSize*1024;rm.chunkCount=rm.getTotalChunks();},logAjaxError:function(jqXHR,textStatus,errorThrown){if(self.resumableUploadOptions.showErrorLog){self._log(logs.ajaxError,{status:jqXHR.status,error:errorThrown,text:jqXHR.responseText||''});}},reset:function(){var rm=self.resumableManager;rm.processed={};},setProcessed:function(status){var rm=self.resumableManager,fm=self.fileManager,id=rm.id,msg,$thumb=rm.$thumb,$prog=rm.$progress,hasThumb=$thumb&&$thumb.length,params={id:hasThumb?$thumb.attr('id'):'',index:fm.getIndex(id),fileId:id};rm.completed=true;rm.lastProgress=0;fm.uploadedSize+=rm.file.size;if(hasThumb){$thumb.removeClass('file-uploading');}if(status==='success'){if(self.showPreview){self._setProgress(101,$prog);self._setThumbStatus($thumb,'Success');self._initUploadSuccess(rm.processed[id].data,$thumb);}self.fileManager.removeFile(id);delete rm.processed[id];self._raise('fileuploaded',[params.id,params.index,params.fileId]);if(fm.isProcessed()){self._setProgress(101);}}else{if(self.showPreview){self._setThumbStatus($thumb,'Error');self._setPreviewError($thumb,true);self._setProgress(101,$prog,self.msgProgressError);self._setProgress(101,self.$progress,self.msgProgressError);self.cancelling=true;}if(!self.$errorContainer.find('li[data-file-id="'+params.fileId+'"]').length){msg=self.msgResumableUploadRetriesExceeded.setTokens({file:rm.fileName,max:self.resumableUploadOptions.maxRetries,error:rm.error});self._showFileError(msg,params);}}if(fm.isProcessed()){rm.reset();}},check:function(){var rm=self.resumableManager,status=true;$.each(rm.logs,function(index,value){if(!value){status=false;return false;}});if(status){clearInterval(rm.chunkIntervalId);rm.setProcessed('success');}},processedResumables:function(){var logs=self.resumableManager.logs,i,count=0;if(!logs||!logs.length){return 0;}for(i=0;i<logs.length;i++){if(logs[i]===true){count++;}}return count;},getUploadedSize:function(){var rm=self.resumableManager,size=rm.processedResumables()*rm.chunkSize;return size>rm.file.size?rm.file.size:size;},getTotalChunks:function(){var rm=self.resumableManager,chunkSize=parseFloat(rm.chunkSize);if(!isNaN(chunkSize)&&chunkSize>0){return Math.ceil(rm.file.size/chunkSize);}return 0;},getProgress:function(){var rm=self.resumableManager,processed=rm.processedResumables(),total=rm.chunkCount;if(total===0){return 0;}return Math.ceil(processed/total*100);},checkAborted:function(intervalId){if(self.paused||self.cancelling){clearInterval(intervalId);self.unlock();}},upload:function(){var rm=self.resumableManager,fm=self.fileManager,ids=fm.getIdList(),flag='new',intervalId;intervalId=setInterval(function(){var id;rm.checkAborted(intervalId);if(flag==='new'){self.lock();flag='processing';id=ids.shift();fm.initStats(id);if(fm.stack[id]){rm.init(id,fm.stack[id],fm.getIndex(id));rm.testUpload();rm.uploadResumable();}}if(!fm.isPending(id)&&rm.completed){flag='new';}if(fm.isProcessed()){var $initThumbs=self.$preview.find('.file-preview-initial');if($initThumbs.length){$h.addCss($initThumbs,$h.SORT_CSS);self._initSortable();}clearInterval(intervalId);self._clearFileInput();self.unlock();setTimeout(function(){var data=self.previewCache.data;if(data){self.initialPreview=data.content;self.initialPreviewConfig=data.config;self.initialPreviewThumbTags=data.tags;}self._raise('filebatchuploadcomplete',[self.initialPreview,self.initialPreviewConfig,self.initialPreviewThumbTags,self._getExtraData()]);},self.processDelay);}},self.processDelay);},uploadResumable:function(){var i,rm=self.resumableManager,total=rm.chunkCount;for(i=0;i<total;i++){rm.logs[i]=!!(rm.processed[rm.id]&&rm.processed[rm.id][i]);}for(i=0;i<total;i++){rm.pushAjax(i,0);}rm.chunkIntervalId=setInterval(rm.loopAjax,self.queueDelay);},testUpload:function(){var rm=self.resumableManager,opts=self.resumableUploadOptions,fd,f,fm=self.fileManager,id=rm.id,fnBefore,fnSuccess,fnError,fnComplete,outData;if(!opts.testUrl){rm.testing=false;return;}rm.testing=true;fd=new FormData();f=fm.stack[id];self._setUploadData(fd,{fileId:id,fileName:f.fileName,fileSize:f.size,fileRelativePath:f.relativePath,chunkSize:rm.chunkSize,chunkCount:rm.chunkCount});fnBefore=function(jqXHR){outData=self._getOutData(fd,jqXHR);self._raise('filetestbeforesend',[id,fm,rm,outData]);};fnSuccess=function(data,textStatus,jqXHR){outData=self._getOutData(fd,jqXHR,data);var pNames=self.uploadParamNames,chunksUploaded=pNames.chunksUploaded||'chunksUploaded',params=[id,fm,rm,outData];if(!data[chunksUploaded]||!$h.isArray(data[chunksUploaded])){self._raise('filetesterror',params);}else{if(!rm.processed[id]){rm.processed[id]={};}$.each(data[chunksUploaded],function(key,index){rm.logs[index]=true;rm.processed[id][index]=true;});rm.processed[id].data=data;self._raise('filetestsuccess',params);}rm.testing=false;};fnError=function(jqXHR,textStatus,errorThrown){outData=self._getOutData(fd,jqXHR);self._raise('filetestajaxerror',[id,fm,rm,outData]);rm.logAjaxError(jqXHR,textStatus,errorThrown);rm.testing=false;};fnComplete=function(){self._raise('filetestcomplete',[id,fm,rm,self._getOutData(fd)]);rm.testing=false;};self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,fd,id,rm.fileIndex,opts.testUrl);},pushAjax:function(index,retry){self.resumableManager.stack.push([index,retry]);},sendAjax:function(index,retry){var fm=self.fileManager,rm=self.resumableManager,opts=self.resumableUploadOptions,f,chunkSize=rm.chunkSize,id=rm.id,file=rm.file,$thumb=rm.$thumb,$btnDelete=rm.$btnDelete;if(rm.processed[id]&&rm.processed[id][index]){return;}rm.currThreads++;if(retry>opts.maxRetries){rm.setProcessed('error');return;}var fd,outData,fnBefore,fnSuccess,fnError,fnComplete,slice=file.slice?'slice':(file.mozSlice?'mozSlice':(file.webkitSlice?'webkitSlice':'slice')),blob=file[slice](chunkSize*index,chunkSize*(index+1));fd=new FormData();f=fm.stack[id];self._setUploadData(fd,{chunkCount:rm.chunkCount,chunkIndex:index,chunkSize:chunkSize,chunkSizeStart:chunkSize*index,fileBlob:[blob,rm.fileName],fileId:id,fileName:rm.fileName,fileRelativePath:f.relativePath,fileSize:file.size,retryCount:retry});if(rm.$progress&&rm.$progress.length){rm.$progress.show();}fnBefore=function(jqXHR){outData=self._getOutData(fd,jqXHR);if(self.showPreview){if(!$thumb.hasClass('file-preview-success')){self._setThumbStatus($thumb,'Loading');$h.addCss($thumb,'file-uploading');}$btnDelete.attr('disabled',true);}self._raise('filechunkbeforesend',[id,index,retry,fm,rm,outData]);};fnSuccess=function(data,textStatus,jqXHR){outData=self._getOutData(fd,jqXHR,data);var paramNames=self.uploadParamNames,chunkIndex=paramNames.chunkIndex||'chunkIndex',opts=self.resumableUploadOptions,params=[id,index,retry,fm,rm,outData];rm.currThreads--;if(data.error){if(opts.showErrorLog){self._log(logs.retryStatus,{retry:retry+1,filename:rm.fileName,chunk:index});}rm.pushAjax(index,retry+1);rm.error=data.error;self._raise('filechunkerror',params);}else{rm.logs[data[chunkIndex]]=true;if(!rm.processed[id]){rm.processed[id]={};}rm.processed[id][data[chunkIndex]]=true;rm.processed[id].data=data;self._raise('filechunksuccess',params);rm.check();}};fnError=function(jqXHR,textStatus,errorThrown){outData=self._getOutData(fd,jqXHR);rm.currThreads--;rm.error=errorThrown;rm.logAjaxError(jqXHR,textStatus,errorThrown);self._raise('filechunkajaxerror',[id,index,retry,fm,rm,outData]);rm.pushAjax(index,retry+1);};fnComplete=function(){self._raise('filechunkcomplete',[id,index,retry,fm,rm,self._getOutData(fd)]);};self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,fd,id,rm.fileIndex);},loopAjax:function(){var rm=self.resumableManager;if(rm.currThreads<self.resumableUploadOptions.maxThreads&&!rm.testing){var arr=rm.stack.shift(),index;if(typeof arr!=='undefined'){index=arr[0];if(!rm.processed[rm.id]||!rm.processed[rm.id][index]){rm.sendAjax(index,arr[1]);}else{if(rm.processedResumables()>=rm.getTotalChunks()){rm.setProcessed('success');clearInterval(rm.chunkIntervalId);}}}}}};self.resumableManager.reset();},_initTemplateDefaults:function(){var self=this,tMain1,tMain2,tPreview,tFileIcon,tClose,tCaption,tBtnDefault,tBtnLink,tBtnBrowse,tModalMain,tModal,tProgress,tSize,tFooter,tActions,tActionDelete,tActionUpload,tActionDownload,tActionZoom,tActionDrag,tIndicator,tTagBef,tTagBef1,tTagBef2,tTagAft,tGeneric,tHtml,tImage,tText,tOffice,tGdocs,tVideo,tAudio,tFlash,tObject,tPdf,tOther,tStyle,tZoomCache,vDefaultDim,tStats;tMain1='{preview}\n'+'<div class="kv-upload-progress kv-hidden"></div><div class="clearfix"></div>\n'+'<div class="input-group {class}">\n'+'  {caption}\n'+'<div class="input-group-btn input-group-append">\n'+'      {remove}\n'+'      {cancel}\n'+'      {pause}\n'+'      {upload}\n'+'      {browse}\n'+'    </div>\n'+'</div>';tMain2='{preview}\n<div class="kv-upload-progress kv-hidden"></div>\n<div class="clearfix"></div>\n'+'{remove}\n{cancel}\n{upload}\n{browse}\n';tPreview='<div class="file-preview {class}">\n'+'    {close}'+'    <div class="{dropClass}">\n'+'    <div class="file-preview-thumbnails">\n'+'    </div>\n'+'    <div class="clearfix"></div>'+'    <div class="file-preview-status text-center text-success"></div>\n'+'    <div class="kv-fileinput-error"></div>\n'+'    </div>\n'+'</div>';tClose=$h.closeButton('fileinput-remove');tFileIcon='<i class="glyphicon glyphicon-file"></i>';tCaption='<div class="file-caption form-control {class}" tabindex="500">\n'+'  <span class="file-caption-icon"></span>\n'+'  <input class="file-caption-name" onkeydown="return false;" onpaste="return false;">\n'+'</div>';tBtnDefault='<button type="{type}" tabindex="500" title="{title}" class="{css}" '+'{status}>{icon} {label}</button>';tBtnLink='<a href="{href}" tabindex="500" title="{title}" class="{css}" {status}>{icon} {label}</a>';tBtnBrowse='<div tabindex="500" class="{css}" {status}>{icon} {label}</div>';tModalMain='<div id="'+$h.MODAL_ID+'" class="file-zoom-dialog modal fade" '+'tabindex="-1" aria-labelledby="'+$h.MODAL_ID+'Label"></div>';tModal='<div class="modal-dialog modal-lg{rtl}" role="document">\n'+'  <div class="modal-content">\n'+'    <div class="modal-header">\n'+'      <h5 class="modal-title">{heading}</h5>\n'+'      <span class="kv-zoom-title"></span>\n'+'      <div class="kv-zoom-actions">{toggleheader}{fullscreen}{borderless}{close}</div>\n'+'    </div>\n'+'    <div class="modal-body">\n'+'      <div class="floating-buttons"></div>\n'+'      <div class="kv-zoom-body file-zoom-content {zoomFrameClass}"></div>\n'+'{prev} {next}\n'+'    </div>\n'+'  </div>\n'+'</div>\n';tProgress='<div class="progress">\n'+'    <div class="{class}" role="progressbar"'+' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n'+'        {status}\n'+'     </div>\n'+'</div>{stats}';tStats='<div class="text-info file-upload-stats">'+'<span class="pending-time">{pendingTime}</span> '+'<span class="upload-speed">{uploadSpeed}</span>'+'</div>';tSize=' <samp>({sizeText})</samp>';tFooter='<div class="file-thumbnail-footer">\n'+'    <div class="file-footer-caption" title="{caption}">\n'+'        <div class="file-caption-info">{caption}</div>\n'+'        <div class="file-size-info">{size}</div>\n'+'    </div>\n'+'    {progress}\n{indicator}\n{actions}\n'+'</div>';tActions='<div class="file-actions">\n'+'    <div class="file-footer-buttons">\n'+'        {download} {upload} {delete} {zoom} {other}'+'    </div>\n'+'</div>\n'+'{drag}\n'+'<div class="clearfix"></div>';tActionDelete='<button type="button" class="kv-file-remove {removeClass}" '+'title="{removeTitle}" {dataUrl}{dataKey}>{removeIcon}</button>\n';tActionUpload='<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">'+'{uploadIcon}</button>';tActionDownload='<a class="kv-file-download {downloadClass}" title="{downloadTitle}" '+'href="{downloadUrl}" download="{caption}" target="_blank">{downloadIcon}</a>';tActionZoom='<button type="button" class="kv-file-zoom {zoomClass}" '+'title="{zoomTitle}">{zoomIcon}</button>';tActionDrag='<span class="file-drag-handle {dragClass}" title="{dragTitle}">{dragIcon}</span>';tIndicator='<div class="file-upload-indicator" title="{indicatorTitle}">{indicator}</div>';tTagBef='<div class="file-preview-frame {frameClass}" id="{previewId}" data-fileindex="{fileindex}"'+' data-fileid="{fileid}" data-template="{template}"';tTagBef1=tTagBef+'><div class="kv-file-content">\n';tTagBef2=tTagBef+' title="{caption}"><div class="kv-file-content">\n';tTagAft='</div>{footer}\n</div>\n';tGeneric='{content}\n';tStyle=' {style}';tHtml='<div class="kv-preview-data file-preview-html" title="{caption}"'+tStyle+'>{data}</div>\n';tImage='<img src="{data}" class="file-preview-image kv-preview-data" title="{title}" '+'alt="{alt}"'+tStyle+'>\n';tText='<textarea class="kv-preview-data file-preview-text" title="{caption}" readonly'+tStyle+'>'+'{data}</textarea>\n';tOffice='<iframe class="kv-preview-data file-preview-office" '+'src="https://view.officeapps.live.com/op/embed.aspx?src={data}"'+tStyle+'></iframe>';tGdocs='<iframe class="kv-preview-data file-preview-gdocs" '+'src="https://docs.google.com/gview?url={data}&embedded=true"'+tStyle+'></iframe>';tVideo='<video class="kv-preview-data file-preview-video" controls'+tStyle+'>\n'+'<source src="{data}" type="{type}">\n'+$h.DEFAULT_PREVIEW+'\n</video>\n';tAudio='<!--suppress ALL --><audio class="kv-preview-data file-preview-audio" controls'+tStyle+'>\n<source src="{data}" '+'type="{type}">\n'+$h.DEFAULT_PREVIEW+'\n</audio>\n';tFlash='<embed class="kv-preview-data file-preview-flash" src="{data}" type="application/x-shockwave-flash"'+tStyle+'>\n';tPdf='<embed class="kv-preview-data file-preview-pdf" src="{data}" type="application/pdf"'+tStyle+'>\n';tObject='<object class="kv-preview-data file-preview-object file-object {typeCss}" '+'data="{data}" type="{type}"'+tStyle+'>\n'+'<param name="movie" value="{caption}" />\n'+$h.OBJECT_PARAMS+' '+$h.DEFAULT_PREVIEW+'\n</object>\n';tOther='<div class="kv-preview-data file-preview-other-frame"'+tStyle+'>\n'+$h.DEFAULT_PREVIEW+'\n</div>\n';tZoomCache='<div class="kv-zoom-cache" style="display:none">{zoomContent}</div>';vDefaultDim={width:'100%',height:'100%','min-height':'480px'};if(self._isPdfRendered()){tPdf=self.pdfRendererTemplate.replace('{renderer}',self._encodeURI(self.pdfRendererUrl));}self.defaults={layoutTemplates:{main1:tMain1,main2:tMain2,preview:tPreview,close:tClose,fileIcon:tFileIcon,caption:tCaption,modalMain:tModalMain,modal:tModal,progress:tProgress,stats:tStats,size:tSize,footer:tFooter,indicator:tIndicator,actions:tActions,actionDelete:tActionDelete,actionUpload:tActionUpload,actionDownload:tActionDownload,actionZoom:tActionZoom,actionDrag:tActionDrag,btnDefault:tBtnDefault,btnLink:tBtnLink,btnBrowse:tBtnBrowse,zoomCache:tZoomCache},previewMarkupTags:{tagBefore1:tTagBef1,tagBefore2:tTagBef2,tagAfter:tTagAft},previewContentTemplates:{generic:tGeneric,html:tHtml,image:tImage,text:tText,office:tOffice,gdocs:tGdocs,video:tVideo,audio:tAudio,flash:tFlash,object:tObject,pdf:tPdf,other:tOther},allowedPreviewTypes:['image','html','text','video','audio','flash','pdf','object'],previewTemplates:{},previewSettings:{image:{width:'auto',height:'auto','max-width':'100%','max-height':'100%'},html:{width:'213px',height:'160px'},text:{width:'213px',height:'160px'},office:{width:'213px',height:'160px'},gdocs:{width:'213px',height:'160px'},video:{width:'213px',height:'160px'},audio:{width:'100%',height:'30px'},flash:{width:'213px',height:'160px'},object:{width:'213px',height:'160px'},pdf:{width:'100%',height:'160px'},other:{width:'213px',height:'160px'}},previewSettingsSmall:{image:{width:'auto',height:'auto','max-width':'100%','max-height':'100%'},html:{width:'100%',height:'160px'},text:{width:'100%',height:'160px'},office:{width:'100%',height:'160px'},gdocs:{width:'100%',height:'160px'},video:{width:'100%',height:'auto'},audio:{width:'100%',height:'30px'},flash:{width:'100%',height:'auto'},object:{width:'100%',height:'auto'},pdf:{width:'100%',height:'160px'},other:{width:'100%',height:'160px'}},previewZoomSettings:{image:{width:'auto',height:'auto','max-width':'100%','max-height':'100%'},html:vDefaultDim,text:vDefaultDim,office:{width:'100%',height:'100%','max-width':'100%','min-height':'480px'},gdocs:{width:'100%',height:'100%','max-width':'100%','min-height':'480px'},video:{width:'auto',height:'100%','max-width':'100%'},audio:{width:'100%',height:'30px'},flash:{width:'auto',height:'480px'},object:{width:'auto',height:'100%','max-width':'100%','min-height':'480px'},pdf:vDefaultDim,other:{width:'auto',height:'100%','min-height':'480px'}},mimeTypeAliases:{'video/quicktime':'video/mp4'},fileTypeSettings:{image:function(vType,vName){return($h.compare(vType,'image.*')&&!$h.compare(vType,/(tiff?|wmf)$/i)||$h.compare(vName,/\.(gif|png|jpe?g)$/i));},html:function(vType,vName){return $h.compare(vType,'text/html')||$h.compare(vName,/\.(htm|html)$/i);},office:function(vType,vName){return $h.compare(vType,/(word|excel|powerpoint|office)$/i)||$h.compare(vName,/\.(docx?|xlsx?|pptx?|pps|potx?)$/i);},gdocs:function(vType,vName){return $h.compare(vType,/(word|excel|powerpoint|office|iwork-pages|tiff?)$/i)||$h.compare(vName,/\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i);},text:function(vType,vName){return $h.compare(vType,'text.*')||$h.compare(vName,/\.(xml|javascript)$/i)||$h.compare(vName,/\.(txt|md|csv|nfo|ini|json|php|js|css)$/i);},video:function(vType,vName){return $h.compare(vType,'video.*')&&($h.compare(vType,/(ogg|mp4|mp?g|mov|webm|3gp)$/i)||$h.compare(vName,/\.(og?|mp4|webm|mp?g|mov|3gp)$/i));},audio:function(vType,vName){return $h.compare(vType,'audio.*')&&($h.compare(vName,/(ogg|mp3|mp?g|wav)$/i)||$h.compare(vName,/\.(og?|mp3|mp?g|wav)$/i));},flash:function(vType,vName){return $h.compare(vType,'application/x-shockwave-flash',true)||$h.compare(vName,/\.(swf)$/i);},pdf:function(vType,vName){return $h.compare(vType,'application/pdf',true)||$h.compare(vName,/\.(pdf)$/i);},object:function(){return true;},other:function(){return true;}},fileActionSettings:{showRemove:true,showUpload:true,showDownload:true,showZoom:true,showDrag:true,removeIcon:'<i class="glyphicon glyphicon-trash"></i>',removeClass:'btn btn-sm btn-kv btn-default btn-outline-secondary',removeErrorClass:'btn btn-sm btn-kv btn-danger',removeTitle:'Remove file',uploadIcon:'<i class="glyphicon glyphicon-upload"></i>',uploadClass:'btn btn-sm btn-kv btn-default btn-outline-secondary',uploadTitle:'Upload file',uploadRetryIcon:'<i class="glyphicon glyphicon-repeat"></i>',uploadRetryTitle:'Retry upload',downloadIcon:'<i class="glyphicon glyphicon-download"></i>',downloadClass:'btn btn-sm btn-kv btn-default btn-outline-secondary',downloadTitle:'Download file',zoomIcon:'<i class="glyphicon glyphicon-zoom-in"></i>',zoomClass:'btn btn-sm btn-kv btn-default btn-outline-secondary',zoomTitle:'View Details',dragIcon:'<i class="glyphicon glyphicon-move"></i>',dragClass:'text-info',dragTitle:'Move / Rearrange',dragSettings:{},indicatorNew:'<i class="glyphicon glyphicon-plus-sign text-warning"></i>',indicatorSuccess:'<i class="glyphicon glyphicon-ok-sign text-success"></i>',indicatorError:'<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',indicatorLoading:'<i class="glyphicon glyphicon-hourglass text-muted"></i>',indicatorPaused:'<i class="glyphicon glyphicon-pause text-primary"></i>',indicatorNewTitle:'Not uploaded yet',indicatorSuccessTitle:'Uploaded',indicatorErrorTitle:'Upload Error',indicatorLoadingTitle:'Uploading ...',indicatorPausedTitle:'Upload Paused'}};$.each(self.defaults,function(key,setting){if(key==='allowedPreviewTypes'){if(self.allowedPreviewTypes===undefined){self.allowedPreviewTypes=setting;}return;}self[key]=$.extend(true,{},setting,self[key]);});self._initPreviewTemplates();},_initPreviewTemplates:function(){var self=this,tags=self.previewMarkupTags,tagBef,tagAft=tags.tagAfter;$.each(self.previewContentTemplates,function(key,value){if($h.isEmpty(self.previewTemplates[key])){tagBef=tags.tagBefore2;if(key==='generic'||key==='image'||key==='html'||key==='text'){tagBef=tags.tagBefore1;}if(self._isPdfRendered()&&key==='pdf'){tagBef=tagBef.replace('kv-file-content','kv-file-content kv-pdf-rendered');}self.previewTemplates[key]=tagBef+value+tagAft;}});},_initPreviewCache:function(){var self=this;self.previewCache={data:{},init:function(){var content=self.initialPreview;if(content.length>0&&!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter);}self.previewCache.data={content:content,config:self.initialPreviewConfig,tags:self.initialPreviewThumbTags};},count:function(skipNull){if(!self.previewCache.data||!self.previewCache.data.content){return 0;}if(skipNull){var chk=self.previewCache.data.content.filter(function(n){return n!==null;});return chk.length;}return self.previewCache.data.content.length;},get:function(i,isDisabled){var ind='init_'+i,data=self.previewCache.data,config=data.config[i],fileId,content=data.content[i],previewId=self.previewInitId+'-'+ind,out,$tmp,cat,ftr,fname,ftype,frameClass,asData=$h.ifSet('previewAsData',config,self.initialPreviewAsData),a=config?{title:config.title||null,alt:config.alt||null}:{title:null,alt:null},parseTemplate=function(cat,dat,fn,ft,id,ftr,ind,fc,t){fc=' file-preview-initial '+$h.SORT_CSS+(fc?' '+fc:'');fileId=config&&config.fileId||'file_'+id;return self._generatePreviewTemplate(cat,dat,fn,ft,id,fileId,false,null,fc,ftr,ind,t,a,config&&config.zoomData||dat);};if(!content||!content.length){return'';}isDisabled=isDisabled===undefined?true:isDisabled;cat=$h.ifSet('type',config,self.initialPreviewFileType||'generic');fname=$h.ifSet('filename',config,$h.ifSet('caption',config));ftype=$h.ifSet('filetype',config,cat);ftr=self.previewCache.footer(i,isDisabled,(config&&config.size||null));frameClass=$h.ifSet('frameClass',config);if(asData){out=parseTemplate(cat,content,fname,ftype,previewId,ftr,ind,frameClass);}else{out=parseTemplate('generic',content,fname,ftype,previewId,ftr,ind,frameClass,cat).setTokens({'content':data.content[i]});}if(data.tags.length&&data.tags[i]){out=$h.replaceTags(out,data.tags[i]);}if(!$h.isEmpty(config)&&!$h.isEmpty(config.frameAttr)){$tmp=$(document.createElement('div')).html(out);$tmp.find('.file-preview-initial').attr(config.frameAttr);out=$tmp.html();$tmp.remove();}return out;},clean:function(data){data.content=$h.cleanArray(data.content);data.config=$h.cleanArray(data.config);data.tags=$h.cleanArray(data.tags);self.previewCache.data=data;},add:function(content,config,tags,append){var data=self.previewCache.data,index=content.length-1;if(!content||!content.length){return index;}if(!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter);}if(append){index=data.content.push(content[0])-1;data.config[index]=config;data.tags[index]=tags;}else{data.content=content;data.config=config;data.tags=tags;}self.previewCache.clean(data);return index;},set:function(content,config,tags,append){var data=self.previewCache.data,i,chk;if(!content||!content.length){return;}if(!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter);}chk=content.filter(function(n){return n!==null;});if(!chk.length){return;}if(data.content===undefined){data.content=[];}if(data.config===undefined){data.config=[];}if(data.tags===undefined){data.tags=[];}if(append){for(i=0;i<content.length;i++){if(content[i]){data.content.push(content[i]);}}for(i=0;i<config.length;i++){if(config[i]){data.config.push(config[i]);}}for(i=0;i<tags.length;i++){if(tags[i]){data.tags.push(tags[i]);}}}else{data.content=content;data.config=config;data.tags=tags;}self.previewCache.clean(data);},unset:function(index){var chk=self.previewCache.count(),rev=self.reversePreviewOrder;if(!chk){return;}if(chk===1){self.previewCache.data.content=[];self.previewCache.data.config=[];self.previewCache.data.tags=[];self.initialPreview=[];self.initialPreviewConfig=[];self.initialPreviewThumbTags=[];return;}self.previewCache.data.content=$h.spliceArray(self.previewCache.data.content,index,rev);self.previewCache.data.config=$h.spliceArray(self.previewCache.data.config,index,rev);self.previewCache.data.tags=$h.spliceArray(self.previewCache.data.tags,index,rev);var data=$.extend(true,{},self.previewCache.data);self.previewCache.clean(data);},out:function(){var html='',caption,len=self.previewCache.count(),i,content;if(len===0){return{content:'',caption:''};}for(i=0;i<len;i++){content=self.previewCache.get(i);html=self.reversePreviewOrder?(content+html):(html+content);}caption=self._getMsgSelected(len);return{content:html,caption:caption};},footer:function(i,isDisabled,size){var data=self.previewCache.data||{};if($h.isEmpty(data.content)){return'';}if($h.isEmpty(data.config)||$h.isEmpty(data.config[i])){data.config[i]={};}isDisabled=isDisabled===undefined?true:isDisabled;var config=data.config[i],caption=$h.ifSet('caption',config),a,width=$h.ifSet('width',config,'auto'),url=$h.ifSet('url',config,false),key=$h.ifSet('key',config,null),fileId=$h.ifSet('fileId',config,null),fs=self.fileActionSettings,initPreviewShowDel=self.initialPreviewShowDelete||false,downloadInitialUrl=!self.initialPreviewDownloadUrl?'':self.initialPreviewDownloadUrl+'?key='+key+(fileId?'&fileId='+fileId:''),dUrl=config.downloadUrl||downloadInitialUrl,dFil=config.filename||config.caption||'',initPreviewShowDwl=!!(dUrl),sDel=$h.ifSet('showRemove',config,$h.ifSet('showRemove',fs,initPreviewShowDel)),sDwl=$h.ifSet('showDownload',config,$h.ifSet('showDownload',fs,initPreviewShowDwl)),sZm=$h.ifSet('showZoom',config,$h.ifSet('showZoom',fs,true)),sDrg=$h.ifSet('showDrag',config,$h.ifSet('showDrag',fs,true)),dis=(url===false)&&isDisabled;sDwl=sDwl&&config.downloadUrl!==false&&!!dUrl;a=self._renderFileActions(config,false,sDwl,sDel,sZm,sDrg,dis,url,key,true,dUrl,dFil);return self._getLayoutTemplate('footer').setTokens({'progress':self._renderThumbProgress(),'actions':a,'caption':caption,'size':self._getSize(size),'width':width,'indicator':''});}};self.previewCache.init();},_isPdfRendered:function(){var self=this,useLib=self.usePdfRenderer,flag=typeof useLib==='function'?useLib():!!useLib;return flag&&self.pdfRendererUrl;},_handler:function($el,event,callback){var self=this,ns=self.namespace,ev=event.split(' ').join(ns+' ')+ns;if(!$el||!$el.length){return;}$el.off(ev).on(ev,callback);},_encodeURI:function(vUrl){var self=this;return self.encodeUrl?encodeURI(vUrl):vUrl;},_log:function(msg,tokens){var self=this,id=self.$element.attr('id');if(id){msg='"'+id+'": '+msg;}msg='bootstrap-fileinput: '+msg;if(typeof tokens==='object'){msg.setTokens(tokens);}if(typeof window.console.log!=='undefined'){window.console.log(msg);}else{window.alert(msg);}},_validate:function(){var self=this,status=self.$element.attr('type')==='file';if(!status){self._log($h.logMessages.badInputType);}return status;},_errorsExist:function(){var self=this,$err,$errList=self.$errorContainer.find('li');if($errList.length){return true;}$err=$(document.createElement('div')).html(self.$errorContainer.html());$err.find('.kv-error-close').remove();$err.find('ul').remove();return!!$.trim($err.text()).length;},_errorHandler:function(evt,caption){var self=this,err=evt.target.error,showError=function(msg){self._showError(msg.replace('{name}',caption));};if(err.code===err.NOT_FOUND_ERR){showError(self.msgFileNotFound);}else{if(err.code===err.SECURITY_ERR){showError(self.msgFileSecured);}else{if(err.code===err.NOT_READABLE_ERR){showError(self.msgFileNotReadable);}else{if(err.code===err.ABORT_ERR){showError(self.msgFilePreviewAborted);}else{showError(self.msgFilePreviewError);}}}}},_addError:function(msg){var self=this,$error=self.$errorContainer;if(msg&&$error.length){$error.html(self.errorCloseButton+msg);self._handler($error.find('.kv-error-close'),'click',function(){setTimeout(function(){if(self.showPreview&&!self.getFrames().length){self.clear();}$error.fadeOut('slow');},self.processDelay);});}},_setValidationError:function(css){var self=this;css=(css?css+' ':'')+'has-error';self.$container.removeClass(css).addClass('has-error');$h.addCss(self.$captionContainer,'is-invalid');},_resetErrors:function(fade){var self=this,$error=self.$errorContainer;self.isError=false;self.$container.removeClass('has-error');self.$captionContainer.removeClass('is-invalid');$error.html('');if(fade){$error.fadeOut('slow');}else{$error.hide();}},_showFolderError:function(folders){var self=this,$error=self.$errorContainer,msg;if(!folders){return;}if(!self.isAjaxUpload){self._clearFileInput();}msg=self.msgFoldersNotAllowed.replace('{n}',folders);self._addError(msg);self._setValidationError();$error.fadeIn(800);self._raise('filefoldererror',[folders,msg]);},_showFileError:function(msg,params,event){var self=this,$error=self.$errorContainer,ev=event||'fileuploaderror',fId=params&&params.fileId||'',e=params&&params.id?'<li data-thumb-id="'+params.id+'" data-file-id="'+fId+'">'+msg+'</li>':'<li>'+msg+'</li>';if($error.find('ul').length===0){self._addError('<ul>'+e+'</ul>');}else{$error.find('ul').append(e);}$error.fadeIn(800);self._raise(ev,[params,msg]);self._setValidationError('file-input-new');return true;},_showError:function(msg,params,event){var self=this,$error=self.$errorContainer,ev=event||'fileerror';params=params||{};params.reader=self.reader;self._addError(msg);$error.fadeIn(800);self._raise(ev,[params,msg]);if(!self.isAjaxUpload){self._clearFileInput();}self._setValidationError('file-input-new');self.$btnUpload.attr('disabled',true);return true;},_noFilesError:function(params){var self=this,label=self.minFileCount>1?self.filePlural:self.fileSingle,msg=self.msgFilesTooLess.replace('{n}',self.minFileCount).replace('{files}',label),$error=self.$errorContainer;self._addError(msg);self.isError=true;self._updateFileDetails(0);$error.fadeIn(800);self._raise('fileerror',[params,msg]);self._clearFileInput();self._setValidationError();},_parseError:function(operation,jqXHR,errorThrown,fileName){var self=this,errMsg=$.trim(errorThrown+''),textPre,text=jqXHR.responseJSON!==undefined&&jqXHR.responseJSON.error!==undefined?jqXHR.responseJSON.error:jqXHR.responseText;if(self.cancelling&&self.msgUploadAborted){errMsg=self.msgUploadAborted;}if(self.showAjaxErrorDetails&&text){text=$.trim(text.replace(/\n\s*\n/g,'\n'));textPre=text.length?'<pre>'+text+'</pre>':'';errMsg+=errMsg?textPre:text;}if(!errMsg){errMsg=self.msgAjaxError.replace('{operation}',operation);}self.cancelling=false;return fileName?'<b>'+fileName+': </b>'+errMsg:errMsg;},_parseFileType:function(type,name){var self=this,isValid,vType,cat,i,types=self.allowedPreviewTypes||[];if(type==='application/text-plain'){return'text';}for(i=0;i<types.length;i++){cat=types[i];isValid=self.fileTypeSettings[cat];vType=isValid(type,name)?cat:'';if(!$h.isEmpty(vType)){return vType;}}return'other';},_getPreviewIcon:function(fname){var self=this,ext,out=null;if(fname&&fname.indexOf('.')>-1){ext=fname.split('.').pop();if(self.previewFileIconSettings){out=self.previewFileIconSettings[ext]||self.previewFileIconSettings[ext.toLowerCase()]||null;}if(self.previewFileExtSettings){$.each(self.previewFileExtSettings,function(key,func){if(self.previewFileIconSettings[key]&&func(ext)){out=self.previewFileIconSettings[key];return;}});}}return out;},_parseFilePreviewIcon:function(content,fname){var self=this,icn=self._getPreviewIcon(fname)||self.previewFileIcon,out=content;if(out.indexOf('{previewFileIcon}')>-1){out=out.setTokens({'previewFileIconClass':self.previewFileIconClass,'previewFileIcon':icn});}return out;},_raise:function(event,params){var self=this,e=$.Event(event);if(params!==undefined){self.$element.trigger(e,params);}else{self.$element.trigger(e);}if(e.isDefaultPrevented()||e.result===false){return false;}switch(event){case'filebatchuploadcomplete':case'filebatchuploadsuccess':case'fileuploaded':case'fileclear':case'filecleared':case'filereset':case'fileerror':case'filefoldererror':case'fileuploaderror':case'filebatchuploaderror':case'filedeleteerror':case'filecustomerror':case'filesuccessremove':break;default:if(!self.ajaxAborted){self.ajaxAborted=e.result;}break;}return true;},_listenFullScreen:function(isFullScreen){var self=this,$modal=self.$modal,$btnFull,$btnBord;if(!$modal||!$modal.length){return;}$btnFull=$modal&&$modal.find('.btn-fullscreen');$btnBord=$modal&&$modal.find('.btn-borderless');if(!$btnFull.length||!$btnBord.length){return;}$btnFull.removeClass('active').attr('aria-pressed','false');$btnBord.removeClass('active').attr('aria-pressed','false');if(isFullScreen){$btnFull.addClass('active').attr('aria-pressed','true');}else{$btnBord.addClass('active').attr('aria-pressed','true');}if($modal.hasClass('file-zoom-fullscreen')){self._maximizeZoomDialog();}else{if(isFullScreen){self._maximizeZoomDialog();}else{$btnBord.removeClass('active').attr('aria-pressed','false');}}},_listen:function(){var self=this,$el=self.$element,$form=self.$form,$cont=self.$container,fullScreenEvents;self._handler($el,'click',function(e){if($el.hasClass('file-no-browse')){if($el.data('zoneClicked')){$el.data('zoneClicked',false);}else{e.preventDefault();}}});self._handler($el,'change',$.proxy(self._change,self));if(self.showBrowse){self._handler(self.$btnFile,'click',$.proxy(self._browse,self));}self._handler($cont.find('.fileinput-remove:not([disabled])'),'click',$.proxy(self.clear,self));self._handler($cont.find('.fileinput-cancel'),'click',$.proxy(self.cancel,self));self._handler($cont.find('.fileinput-pause'),'click',$.proxy(self.pause,self));self._initDragDrop();self._handler($form,'reset',$.proxy(self.clear,self));if(!self.isAjaxUpload){self._handler($form,'submit',$.proxy(self._submitForm,self));}self._handler(self.$container.find('.fileinput-upload'),'click',$.proxy(self._uploadClick,self));self._handler($(window),'resize',function(){self._listenFullScreen(screen.width===window.innerWidth&&screen.height===window.innerHeight);});fullScreenEvents='webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange';self._handler($(document),fullScreenEvents,function(){self._listenFullScreen($h.checkFullScreen());});self._autoFitContent();self._initClickable();self._refreshPreview();},_autoFitContent:function(){var width=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,self=this,config=width<400?(self.previewSettingsSmall||self.defaults.previewSettingsSmall):(self.previewSettings||self.defaults.previewSettings),sel;$.each(config,function(cat,settings){sel='.file-preview-frame .file-preview-'+cat;self.$preview.find(sel+'.kv-preview-data,'+sel+' .kv-preview-data').css(settings);});},_scanDroppedItems:function(item,files,path){path=path||'';var self=this,i,dirReader,readDir,errorHandler=function(e){self._log($h.logMessages.badDroppedFiles);self._log(e);};if(item.isFile){item.file(function(file){files.push(file);},errorHandler);}else{if(item.isDirectory){dirReader=item.createReader();readDir=function(){dirReader.readEntries(function(entries){if(entries&&entries.length>0){for(i=0;i<entries.length;i++){self._scanDroppedItems(entries[i],files,path+item.name+'/');}readDir();}return null;},errorHandler);};readDir();}}},_initDragDrop:function(){var self=this,$zone=self.$dropZone;if(self.dropZoneEnabled&&self.showPreview){self._handler($zone,'dragenter dragover',$.proxy(self._zoneDragEnter,self));self._handler($zone,'dragleave',$.proxy(self._zoneDragLeave,self));self._handler($zone,'drop',$.proxy(self._zoneDrop,self));self._handler($(document),'dragenter dragover drop',self._zoneDragDropInit);}},_zoneDragDropInit:function(e){e.stopPropagation();e.preventDefault();},_zoneDragEnter:function(e){var self=this,dataTransfer=e.originalEvent.dataTransfer,hasFiles=$.inArray('Files',dataTransfer.types)>-1;self._zoneDragDropInit(e);if(self.isDisabled||!hasFiles){e.originalEvent.dataTransfer.effectAllowed='none';e.originalEvent.dataTransfer.dropEffect='none';return;}if(self._raise('fileDragEnter',{'sourceEvent':e,'files':dataTransfer.types.Files})){$h.addCss(self.$dropZone,'file-highlighted');}},_zoneDragLeave:function(e){var self=this;self._zoneDragDropInit(e);if(self.isDisabled){return;}if(self._raise('fileDragLeave',{'sourceEvent':e})){self.$dropZone.removeClass('file-highlighted');}},_zoneDrop:function(e){var self=this,i,$el=self.$element,dataTransfer=e.originalEvent.dataTransfer,files=dataTransfer.files,items=dataTransfer.items,folders=$h.getDragDropFolders(items),processFiles=function(){if(!self.isAjaxUpload){self.changeTriggered=true;$el.get(0).files=files;setTimeout(function(){self.changeTriggered=false;$el.trigger('change'+self.namespace);},self.processDelay);}else{self._change(e,files);}self.$dropZone.removeClass('file-highlighted');};e.preventDefault();if(self.isDisabled||$h.isEmpty(files)){return;}if(!self._raise('fileDragDrop',{'sourceEvent':e,'files':files})){return;}if(folders>0){if(!self.isAjaxUpload){self._showFolderError(folders);return;}files=[];for(i=0;i<items.length;i++){var item=items[i].webkitGetAsEntry();if(item){self._scanDroppedItems(item,files);}}setTimeout(function(){processFiles();},500);}else{processFiles();}},_uploadClick:function(e){var self=this,$btn=self.$container.find('.fileinput-upload'),$form,isEnabled=!$btn.hasClass('disabled')&&$h.isEmpty($btn.attr('disabled'));if(e&&e.isDefaultPrevented()){return;}if(!self.isAjaxUpload){if(isEnabled&&$btn.attr('type')!=='submit'){$form=$btn.closest('form');if($form.length){$form.trigger('submit');}e.preventDefault();}return;}e.preventDefault();if(isEnabled){self.upload();}},_submitForm:function(){var self=this;return self._isFileSelectionValid()&&!self._abort({});},_clearPreview:function(){var self=this,$p=self.$preview,$thumbs=self.showUploadedThumbs?self.getFrames(':not(.file-preview-success)'):self.getFrames();$thumbs.each(function(){var $thumb=$(this);$thumb.remove();$h.cleanZoomCache($p.find('#zoom-'+$thumb.attr('id')));});if(!self.getFrames().length||!self.showPreview){self._resetUpload();}self._validateDefaultPreview();},_initSortable:function(){var self=this,$el=self.$preview,settings,selector='.'+$h.SORT_CSS,rev=self.reversePreviewOrder;if(!window.KvSortable||$el.find(selector).length===0){return;}settings={handle:'.drag-handle-init',dataIdAttr:'data-preview-id',scroll:false,draggable:selector,onSort:function(e){var oldIndex=e.oldIndex,newIndex=e.newIndex,i=0;self.initialPreview=$h.moveArray(self.initialPreview,oldIndex,newIndex,rev);self.initialPreviewConfig=$h.moveArray(self.initialPreviewConfig,oldIndex,newIndex,rev);self.previewCache.init();self.getFrames('.file-preview-initial').each(function(){$(this).attr('data-fileindex','init_'+i);i++;});self._raise('filesorted',{previewId:$(e.item).attr('id'),'oldIndex':oldIndex,'newIndex':newIndex,stack:self.initialPreviewConfig});}};if($el.data('kvsortable')){$el.kvsortable('destroy');}$.extend(true,settings,self.fileActionSettings.dragSettings);$el.kvsortable(settings);},_setPreviewContent:function(content){var self=this;self.$preview.html(content);self._autoFitContent();},_initPreviewImageOrientations:function(){var self=this,i=0;if(!self.autoOrientImageInitial){return;}self.getFrames('.file-preview-initial').each(function(){var $thumb=$(this),$img,$zoomImg,id,config=self.initialPreviewConfig[i];if(config&&config.exif&&config.exif.Orientation){id=$thumb.attr('id');$img=$thumb.find('>.kv-file-content img');$zoomImg=self.$preview.find('#zoom-'+id+' >.kv-file-content img');self.setImageOrientation($img,$zoomImg,config.exif.Orientation,$thumb);}i++;});},_initPreview:function(isInit){var self=this,cap=self.initialCaption||'',out;if(!self.previewCache.count(true)){self._clearPreview();if(isInit){self._setCaption(cap);}else{self._initCaption();}return;}out=self.previewCache.out();cap=isInit&&self.initialCaption?self.initialCaption:out.caption;self._setPreviewContent(out.content);self._setInitThumbAttr();self._setCaption(cap);self._initSortable();if(!$h.isEmpty(out.content)){self.$container.removeClass('file-input-new');}self._initPreviewImageOrientations();},_getZoomButton:function(type){var self=this,label=self.previewZoomButtonIcons[type],css=self.previewZoomButtonClasses[type],title=' title="'+(self.previewZoomButtonTitles[type]||'')+'" ',params=title+(type==='close'?' data-dismiss="modal" aria-hidden="true"':'');if(type==='fullscreen'||type==='borderless'||type==='toggleheader'){params+=' data-toggle="button" aria-pressed="false" autocomplete="off"';}return'<button type="button" class="'+css+' btn-'+type+'"'+params+'>'+label+'</button>';},_getModalContent:function(){var self=this;return self._getLayoutTemplate('modal').setTokens({'rtl':self.rtl?' kv-rtl':'','zoomFrameClass':self.frameClass,'heading':self.msgZoomModalHeading,'prev':self._getZoomButton('prev'),'next':self._getZoomButton('next'),'toggleheader':self._getZoomButton('toggleheader'),'fullscreen':self._getZoomButton('fullscreen'),'borderless':self._getZoomButton('borderless'),'close':self._getZoomButton('close')});},_listenModalEvent:function(event){var self=this,$modal=self.$modal,getParams=function(e){return{sourceEvent:e,previewId:$modal.data('previewId'),modal:$modal};};$modal.on(event+'.bs.modal',function(e){var $btnFull=$modal.find('.btn-fullscreen'),$btnBord=$modal.find('.btn-borderless');self._raise('filezoom'+event,getParams(e));if(event==='shown'){$btnBord.removeClass('active').attr('aria-pressed','false');$btnFull.removeClass('active').attr('aria-pressed','false');if($modal.hasClass('file-zoom-fullscreen')){self._maximizeZoomDialog();if($h.checkFullScreen()){$btnFull.addClass('active').attr('aria-pressed','true');}else{$btnBord.addClass('active').attr('aria-pressed','true');}}}});},_initZoom:function(){var self=this,$dialog,modalMain=self._getLayoutTemplate('modalMain'),modalId='#'+$h.MODAL_ID;if(!self.showPreview){return;}self.$modal=$(modalId);if(!self.$modal||!self.$modal.length){$dialog=$(document.createElement('div')).html(modalMain).insertAfter(self.$container);self.$modal=$(modalId).insertBefore($dialog);$dialog.remove();}$h.initModal(self.$modal);self.$modal.html(self._getModalContent());$.each($h.MODAL_EVENTS,function(key,event){self._listenModalEvent(event);});},_initZoomButtons:function(){var self=this,previewId=self.$modal.data('previewId')||'',$first,$last,thumbs=self.getFrames().toArray(),len=thumbs.length,$prev=self.$modal.find('.btn-prev'),$next=self.$modal.find('.btn-next');if(thumbs.length<2){$prev.hide();$next.hide();return;}else{$prev.show();$next.show();}if(!len){return;}$first=$(thumbs[0]);$last=$(thumbs[len-1]);$prev.removeAttr('disabled');$next.removeAttr('disabled');if($first.length&&$first.attr('id')===previewId){$prev.attr('disabled',true);}if($last.length&&$last.attr('id')===previewId){$next.attr('disabled',true);}},_maximizeZoomDialog:function(){var self=this,$modal=self.$modal,$head=$modal.find('.modal-header:visible'),$foot=$modal.find('.modal-footer:visible'),$body=$modal.find('.modal-body'),h=$(window).height(),diff=0;$modal.addClass('file-zoom-fullscreen');if($head&&$head.length){h-=$head.outerHeight(true);}if($foot&&$foot.length){h-=$foot.outerHeight(true);}if($body&&$body.length){diff=$body.outerHeight(true)-$body.height();h-=diff;}$modal.find('.kv-zoom-body').height(h);},_resizeZoomDialog:function(fullScreen){var self=this,$modal=self.$modal,$btnFull=$modal.find('.btn-fullscreen'),$btnBord=$modal.find('.btn-borderless');if($modal.hasClass('file-zoom-fullscreen')){$h.toggleFullScreen(false);if(!fullScreen){if(!$btnFull.hasClass('active')){$modal.removeClass('file-zoom-fullscreen');self.$modal.find('.kv-zoom-body').css('height',self.zoomModalHeight);}else{$btnFull.removeClass('active').attr('aria-pressed','false');}}else{if(!$btnFull.hasClass('active')){$modal.removeClass('file-zoom-fullscreen');self._resizeZoomDialog(true);if($btnBord.hasClass('active')){$btnBord.removeClass('active').attr('aria-pressed','false');}}}}else{if(!fullScreen){self._maximizeZoomDialog();return;}$h.toggleFullScreen(true);}$modal.focus();},_setZoomContent:function($frame,animate){var self=this,$content,tmplt,body,title,$body,$dataEl,config,previewId=$frame.attr('id'),$zoomPreview=self.$preview.find('#zoom-'+previewId),$modal=self.$modal,$tmp,$btnFull=$modal.find('.btn-fullscreen'),$btnBord=$modal.find('.btn-borderless'),cap,size,$btnTogh=$modal.find('.btn-toggleheader');tmplt=$zoomPreview.attr('data-template')||'generic';$content=$zoomPreview.find('.kv-file-content');body=$content.length?$content.html():'';cap=$frame.data('caption')||'';size=$frame.data('size')||'';title=cap+' '+size;$modal.find('.kv-zoom-title').attr('title',$('<div/>').html(title).text()).html(title);$body=$modal.find('.kv-zoom-body');$modal.removeClass('kv-single-content');if(animate){$tmp=$body.addClass('file-thumb-loading').clone().insertAfter($body);$body.html(body).hide();$tmp.fadeOut('fast',function(){$body.fadeIn('fast',function(){$body.removeClass('file-thumb-loading');});$tmp.remove();});}else{$body.html(body);}config=self.previewZoomSettings[tmplt];if(config){$dataEl=$body.find('.kv-preview-data');$h.addCss($dataEl,'file-zoom-detail');$.each(config,function(key,value){$dataEl.css(key,value);if(($dataEl.attr('width')&&key==='width')||($dataEl.attr('height')&&key==='height')){$dataEl.removeAttr(key);}});}$modal.data('previewId',previewId);self._handler($modal.find('.btn-prev'),'click',function(){self._zoomSlideShow('prev',previewId);});self._handler($modal.find('.btn-next'),'click',function(){self._zoomSlideShow('next',previewId);});self._handler($btnFull,'click',function(){self._resizeZoomDialog(true);});self._handler($btnBord,'click',function(){self._resizeZoomDialog(false);});self._handler($btnTogh,'click',function(){var $header=$modal.find('.modal-header'),$floatBar=$modal.find('.modal-body .floating-buttons'),ht,$actions=$header.find('.kv-zoom-actions'),resize=function(height){var $body=self.$modal.find('.kv-zoom-body'),h=self.zoomModalHeight;if($modal.hasClass('file-zoom-fullscreen')){h=$body.outerHeight(true);if(!height){h=h-$header.outerHeight(true);}}$body.css('height',height?h+height:h);};if($header.is(':visible')){ht=$header.outerHeight(true);$header.slideUp('slow',function(){$actions.find('.btn').appendTo($floatBar);resize(ht);});}else{$floatBar.find('.btn').appendTo($actions);$header.slideDown('slow',function(){resize();});}$modal.focus();});self._handler($modal,'keydown',function(e){var key=e.which||e.keyCode,$prev=$(this).find('.btn-prev'),$next=$(this).find('.btn-next'),vId=$(this).data('previewId'),vPrevKey=self.rtl?39:37,vNextKey=self.rtl?37:39;if(key===vPrevKey&&$prev.length&&!$prev.attr('disabled')){self._zoomSlideShow('prev',vId);}if(key===vNextKey&&$next.length&&!$next.attr('disabled')){self._zoomSlideShow('next',vId);}});},_zoomPreview:function($btn){var self=this,$frame,$modal=self.$modal;if(!$btn.length){throw'Cannot zoom to detailed preview!';}$h.initModal($modal);$modal.html(self._getModalContent());$frame=$btn.closest($h.FRAMES);self._setZoomContent($frame);$modal.modal('show');self._initZoomButtons();},_zoomSlideShow:function(dir,previewId){var self=this,$btn=self.$modal.find('.kv-zoom-actions .btn-'+dir),$targFrame,i,thumbs=self.getFrames().toArray(),len=thumbs.length,out;if($btn.attr('disabled')){return;}for(i=0;i<len;i++){if($(thumbs[i]).attr('id')===previewId){out=dir==='prev'?i-1:i+1;break;}}if(out<0||out>=len||!thumbs[out]){return;}$targFrame=$(thumbs[out]);if($targFrame.length){self._setZoomContent($targFrame,true);}self._initZoomButtons();self._raise('filezoom'+dir,{'previewId':previewId,modal:self.$modal});},_initZoomButton:function(){var self=this;self.$preview.find('.kv-file-zoom').each(function(){var $el=$(this);self._handler($el,'click',function(){self._zoomPreview($el);});});},_inputFileCount:function(){return this.$element.get(0).files.length;},_refreshPreview:function(){var self=this,files;if((!self._inputFileCount()&&!self.isAjaxUpload)||!self.showPreview||!self.isPreviewable){return;}if(self.isAjaxUpload){if(self.fileManager.count()>0){files=$.extend(true,{},self.fileManager.stack);self.fileManager.clear();self._clearFileInput();}else{files=self.$element.get(0).files;}}else{files=self.$element.get(0).files;}if(files&&files.length){self.readFiles(files);self._setFileDropZoneTitle();}},_clearObjects:function($el){$el.find('video audio').each(function(){this.pause();$(this).remove();});$el.find('img object div').each(function(){$(this).remove();});},_clearFileInput:function(){var self=this,$el=self.$element,$srcFrm,$tmpFrm,$tmpEl;if(!self._inputFileCount()){return;}$srcFrm=$el.closest('form');$tmpFrm=$(document.createElement('form'));$tmpEl=$(document.createElement('div'));$el.before($tmpEl);if($srcFrm.length){$srcFrm.after($tmpFrm);}else{$tmpEl.after($tmpFrm);}$tmpFrm.append($el).trigger('reset');$tmpEl.before($el).remove();$tmpFrm.remove();},_resetUpload:function(){var self=this;self.uploadCache={content:[],config:[],tags:[],append:true};self.$btnUpload.removeAttr('disabled');self._setProgress(0);self.$progress.hide();self._resetErrors(false);self._initAjax();self.fileManager.clearImages();self._resetCanvas();self.cacheInitialPreview={};if(self.overwriteInitial){self.initialPreview=[];self.initialPreviewConfig=[];self.initialPreviewThumbTags=[];self.previewCache.data={content:[],config:[],tags:[]};}},_resetCanvas:function(){var self=this;if(self.canvas&&self.imageCanvasContext){self.imageCanvasContext.clearRect(0,0,self.canvas.width,self.canvas.height);}},_hasInitialPreview:function(){var self=this;return!self.overwriteInitial&&self.previewCache.count(true);},_resetPreview:function(){var self=this,out,cap;if(self.previewCache.count(true)){out=self.previewCache.out();self._setPreviewContent(out.content);self._setInitThumbAttr();cap=self.initialCaption?self.initialCaption:out.caption;self._setCaption(cap);}else{self._clearPreview();self._initCaption();}if(self.showPreview){self._initZoom();self._initSortable();}},_clearDefaultPreview:function(){var self=this;self.$preview.find('.file-default-preview').remove();},_validateDefaultPreview:function(){var self=this;if(!self.showPreview||$h.isEmpty(self.defaultPreviewContent)){return;}self._setPreviewContent('<div class="file-default-preview">'+self.defaultPreviewContent+'</div>');self.$container.removeClass('file-input-new');self._initClickable();},_resetPreviewThumbs:function(isAjax){var self=this,out;if(isAjax){self._clearPreview();self.clearFileStack();return;}if(self._hasInitialPreview()){out=self.previewCache.out();self._setPreviewContent(out.content);self._setInitThumbAttr();self._setCaption(out.caption);self._initPreviewActions();}else{self._clearPreview();}},_getLayoutTemplate:function(t){var self=this,template=self.layoutTemplates[t];if($h.isEmpty(self.customLayoutTags)){return template;}return $h.replaceTags(template,self.customLayoutTags);},_getPreviewTemplate:function(t){var self=this,template=self.previewTemplates[t];if($h.isEmpty(self.customPreviewTags)){return template;}return $h.replaceTags(template,self.customPreviewTags);},_getOutData:function(formdata,jqXHR,responseData,filesData){var self=this;jqXHR=jqXHR||{};responseData=responseData||{};filesData=filesData||self.fileManager.list();return{formdata:formdata,files:filesData,filenames:self.filenames,filescount:self.getFilesCount(),extra:self._getExtraData(),response:responseData,reader:self.reader,jqXHR:jqXHR};},_getMsgSelected:function(n){var self=this,strFiles=n===1?self.fileSingle:self.filePlural;return n>0?self.msgSelected.replace('{n}',n).replace('{files}',strFiles):self.msgNoFilesSelected;},_getFrame:function(id){var self=this,$frame=$('#'+id);if(!$frame.length){self._log($h.logMessages.invalidThumb,{id:id});return null;}return $frame;},_getThumbs:function(css){css=css||'';return this.getFrames(':not(.file-preview-initial)'+css);},_getExtraData:function(fileId,index){var self=this,data=self.uploadExtraData;if(typeof self.uploadExtraData==='function'){data=self.uploadExtraData(fileId,index);}return data;},_initXhr:function(xhrobj,fileId,fileCount){var self=this,fm=self.fileManager,func=function(event){var pct=0,total=event.total,loaded=event.loaded||event.position,stats=fm.getUploadStats(fileId,loaded,total);if(event.lengthComputable&&!self.enableResumableUpload){pct=$h.round(loaded/total*100);}if(fileId){self._setFileUploadStats(fileId,pct,fileCount,stats);}else{self._setProgress(pct,null,null,self._getStats(stats));}self._raise('fileajaxprogress',[stats]);};if(xhrobj.upload){if(self.progressDelay){func=$h.debounce(func,self.progressDelay);}xhrobj.upload.addEventListener('progress',func,false);}return xhrobj;},_initAjaxSettings:function(){var self=this;self._ajaxSettings=$.extend(true,{},self.ajaxSettings);self._ajaxDeleteSettings=$.extend(true,{},self.ajaxDeleteSettings);},_mergeAjaxCallback:function(funcName,srcFunc,type){var self=this,settings=self._ajaxSettings,flag=self.mergeAjaxCallbacks,targFunc;if(type==='delete'){settings=self._ajaxDeleteSettings;flag=self.mergeAjaxDeleteCallbacks;}targFunc=settings[funcName];if(flag&&typeof targFunc==='function'){if(flag==='before'){settings[funcName]=function(){targFunc.apply(this,arguments);srcFunc.apply(this,arguments);};}else{settings[funcName]=function(){srcFunc.apply(this,arguments);targFunc.apply(this,arguments);};}}else{settings[funcName]=srcFunc;}},_ajaxSubmit:function(fnBefore,fnSuccess,fnComplete,fnError,formdata,fileId,index,vUrl){var self=this,settings,defaults,data,processQueue;if(!self._raise('filepreajax',[formdata,fileId,index])){return;}formdata.append('initialPreview',JSON.stringify(self.initialPreview));formdata.append('initialPreviewConfig',JSON.stringify(self.initialPreviewConfig));formdata.append('initialPreviewThumbTags',JSON.stringify(self.initialPreviewThumbTags));self._initAjaxSettings();self._mergeAjaxCallback('beforeSend',fnBefore);self._mergeAjaxCallback('success',fnSuccess);self._mergeAjaxCallback('complete',fnComplete);self._mergeAjaxCallback('error',fnError);vUrl=vUrl||self.uploadUrlThumb||self.uploadUrl;if(typeof vUrl==='function'){vUrl=vUrl();}data=self._getExtraData(fileId,index)||{};if(typeof data==='object'){$.each(data,function(key,value){formdata.append(key,value);});}defaults={xhr:function(){var xhrobj=$.ajaxSettings.xhr();return self._initXhr(xhrobj,fileId,self.fileManager.count());},url:self._encodeURI(vUrl),type:'POST',dataType:'json',data:formdata,cache:false,processData:false,contentType:false};settings=$.extend(true,{},defaults,self._ajaxSettings);self.ajaxQueue.push(settings);processQueue=function(){var config,xhr;if(self.ajaxCurrentThreads<self.maxAjaxThreads){config=self.ajaxQueue.shift();if(typeof config!=='undefined'){self.ajaxCurrentThreads++;xhr=$.ajax(config).done(function(){clearInterval(self.ajaxQueueIntervalId);self.ajaxCurrentThreads--;});self.ajaxRequests.push(xhr);}}};self.ajaxQueueIntervalId=setInterval(processQueue,self.queueDelay);},_mergeArray:function(prop,content){var self=this,arr1=$h.cleanArray(self[prop]),arr2=$h.cleanArray(content);self[prop]=arr1.concat(arr2);},_initUploadSuccess:function(out,$thumb,allFiles){var self=this,append,data,index,$div,$newCache,content,config,tags,i;if(!self.showPreview||typeof out!=='object'||$.isEmptyObject(out)){return;}if(out.initialPreview!==undefined&&out.initialPreview.length>0){self.hasInitData=true;content=out.initialPreview||[];config=out.initialPreviewConfig||[];tags=out.initialPreviewThumbTags||[];append=out.append===undefined||out.append;if(content.length>0&&!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter);}if(content.length){self._mergeArray('initialPreview',content);self._mergeArray('initialPreviewConfig',config);self._mergeArray('initialPreviewThumbTags',tags);}if($thumb!==undefined){if(!allFiles){index=self.previewCache.add(content[0],config[0],tags[0],append);data=self.previewCache.get(index,false);$div=$(document.createElement('div')).html(data).hide().insertAfter($thumb);$newCache=$div.find('.kv-zoom-cache');if($newCache&&$newCache.length){$newCache.insertAfter($thumb);}$thumb.fadeOut('slow',function(){var $newThumb=$div.find('.file-preview-frame');if($newThumb&&$newThumb.length){$newThumb.insertBefore($thumb).fadeIn('slow').css('display:inline-block');}self._initPreviewActions();self._clearFileInput();$h.cleanZoomCache(self.$preview.find('#zoom-'+$thumb.attr('id')));$thumb.remove();$div.remove();self._initSortable();});}else{i=$thumb.attr('data-fileindex');self.uploadCache.content[i]=content[0];self.uploadCache.config[i]=config[0]||[];self.uploadCache.tags[i]=tags[0]||[];self.uploadCache.append=append;}}else{self.previewCache.set(content,config,tags,append);self._initPreview();self._initPreviewActions();}}},_initSuccessThumbs:function(){var self=this;if(!self.showPreview){return;}self._getThumbs($h.FRAMES+'.file-preview-success').each(function(){var $thumb=$(this),$preview=self.$preview,$remove=$thumb.find('.kv-file-remove');$remove.removeAttr('disabled');self._handler($remove,'click',function(){var id=$thumb.attr('id'),out=self._raise('filesuccessremove',[id,$thumb.attr('data-fileindex')]);$h.cleanMemory($thumb);if(out===false){return;}$thumb.fadeOut('slow',function(){$h.cleanZoomCache($preview.find('#zoom-'+id));$thumb.remove();if(!self.getFrames().length){self.reset();}});});});},_updateInitialPreview:function(){var self=this,u=self.uploadCache,i,j,len=0,data=self.cacheInitialPreview;if(data&&data.content){len=data.content.length;}if(self.showPreview){self.previewCache.set(u.content,u.config,u.tags,u.append);if(len){for(i=0;i<u.content.length;i++){j=i+len;data.content[j]=u.content[i];if(data.config.length){data.config[j]=u.config[i];}if(data.tags.length){data.tags[j]=u.tags[i];}}self.initialPreview=$h.cleanArray(data.content);self.initialPreviewConfig=$h.cleanArray(data.config);self.initialPreviewThumbTags=$h.cleanArray(data.tags);}else{self.initialPreview=u.content;self.initialPreviewConfig=u.config;self.initialPreviewThumbTags=u.tags;}self.cacheInitialPreview={};if(self.hasInitData){self._initPreview();self._initPreviewActions();}}},_uploadSingle:function(i,id,isBatch){var self=this,fm=self.fileManager,count=fm.count(),formdata=new FormData(),outData,previewId=self.previewInitId+'-'+i,$thumb,chkComplete,$btnUpload,$btnDelete,hasPostData=count>0||!$.isEmptyObject(self.uploadExtraData),uploadFailed,$prog,fnBefore,errMsg,fnSuccess,fnComplete,fnError,updateUploadLog,op=self.ajaxOperations.uploadThumb,fileObj=fm.getFile(id),params={id:previewId,index:i,fileId:id},fileName=self.fileManager.getFileName(id,true);if(self.enableResumableUpload){return;}if(self.showPreview){$thumb=self.fileManager.getThumb(id);$prog=$thumb.find('.file-thumb-progress');$btnUpload=$thumb.find('.kv-file-upload');$btnDelete=$thumb.find('.kv-file-remove');$prog.show();}if(count===0||!hasPostData||(self.showPreview&&$btnUpload&&$btnUpload.hasClass('disabled'))||self._abort(params)){return;}updateUploadLog=function(){if(!uploadFailed){fm.removeFile(id);}else{fm.errors.push(id);}fm.setProcessed(id);if(fm.isProcessed()){self.fileBatchCompleted=true;}};chkComplete=function(){var $initThumbs;if(!self.fileBatchCompleted){return;}setTimeout(function(){var triggerReset=fm.count()===0,errCount=fm.errors.length;self._updateInitialPreview();self.unlock(triggerReset);if(triggerReset){self._clearFileInput();}$initThumbs=self.$preview.find('.file-preview-initial');if(self.uploadAsync&&$initThumbs.length){$h.addCss($initThumbs,$h.SORT_CSS);self._initSortable();}self._raise('filebatchuploadcomplete',[fm.stack,self._getExtraData()]);if(!self.retryErrorUploads||errCount===0){fm.clear();}self._setProgress(101);self.ajaxAborted=false;},self.processDelay);};fnBefore=function(jqXHR){outData=self._getOutData(formdata,jqXHR);fm.initStats(id);self.fileBatchCompleted=false;if(!isBatch){self.ajaxAborted=false;}if(self.showPreview){if(!$thumb.hasClass('file-preview-success')){self._setThumbStatus($thumb,'Loading');$h.addCss($thumb,'file-uploading');}$btnUpload.attr('disabled',true);$btnDelete.attr('disabled',true);}if(!isBatch){self.lock();}if(fm.errors.indexOf(id)!==-1){delete fm.errors[id];}self._raise('filepreupload',[outData,previewId,i]);$.extend(true,params,outData);if(self._abort(params)){jqXHR.abort();if(!isBatch){self._setThumbStatus($thumb,'New');$thumb.removeClass('file-uploading');$btnUpload.removeAttr('disabled');$btnDelete.removeAttr('disabled');self.unlock();}self._setProgressCancelled();}};fnSuccess=function(data,textStatus,jqXHR){var pid=self.showPreview&&$thumb.attr('id')?$thumb.attr('id'):previewId;outData=self._getOutData(formdata,jqXHR,data);$.extend(true,params,outData);setTimeout(function(){if($h.isEmpty(data)||$h.isEmpty(data.error)){if(self.showPreview){self._setThumbStatus($thumb,'Success');$btnUpload.hide();self._initUploadSuccess(data,$thumb,isBatch);self._setProgress(101,$prog);}self._raise('fileuploaded',[outData,pid,i]);if(!isBatch){self.fileManager.remove($thumb);}else{updateUploadLog();}}else{uploadFailed=true;errMsg=self._parseError(op,jqXHR,self.msgUploadError,self.fileManager.getFileName(id));self._showFileError(errMsg,params);self._setPreviewError($thumb,true);if(!self.retryErrorUploads){$btnUpload.hide();}if(isBatch){updateUploadLog();}self._setProgress(101,$('#'+pid).find('.file-thumb-progress'),self.msgUploadError);}},self.processDelay);};fnComplete=function(){setTimeout(function(){if(self.showPreview){$btnUpload.removeAttr('disabled');$btnDelete.removeAttr('disabled');$thumb.removeClass('file-uploading');}if(!isBatch){self.unlock(false);self._clearFileInput();}else{chkComplete();}self._initSuccessThumbs();},self.processDelay);};fnError=function(jqXHR,textStatus,errorThrown){errMsg=self._parseError(op,jqXHR,errorThrown,self.fileManager.getFileName(id));uploadFailed=true;setTimeout(function(){if(isBatch){updateUploadLog();}self.fileManager.setProgress(id,100);self._setPreviewError($thumb,true);if(!self.retryErrorUploads){$btnUpload.hide();}$.extend(true,params,self._getOutData(formdata,jqXHR));self._setProgress(101,$prog,self.msgAjaxProgressError.replace('{operation}',op));self._setProgress(101,$thumb.find('.file-thumb-progress'),self.msgUploadError);self._showFileError(errMsg,params);},self.processDelay);};formdata.append(self.uploadFileAttr,fileObj.file,fileName);self._setUploadData(formdata,{fileId:id});self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,formdata,id,i);},_uploadBatch:function(){var self=this,fm=self.fileManager,total=fm.total(),params={},fnBefore,fnSuccess,fnError,fnComplete,hasPostData=total>0||!$.isEmptyObject(self.uploadExtraData),errMsg,setAllUploaded,formdata=new FormData(),op=self.ajaxOperations.uploadBatch;if(total===0||!hasPostData||self._abort(params)){return;}setAllUploaded=function(){self.fileManager.clear();self._clearFileInput();};fnBefore=function(jqXHR){self.lock();fm.initStats();var outData=self._getOutData(formdata,jqXHR);self.ajaxAborted=false;if(self.showPreview){self._getThumbs().each(function(){var $thumb=$(this),$btnUpload=$thumb.find('.kv-file-upload'),$btnDelete=$thumb.find('.kv-file-remove');if(!$thumb.hasClass('file-preview-success')){self._setThumbStatus($thumb,'Loading');$h.addCss($thumb,'file-uploading');}$btnUpload.attr('disabled',true);$btnDelete.attr('disabled',true);});}self._raise('filebatchpreupload',[outData]);if(self._abort(outData)){jqXHR.abort();self._getThumbs().each(function(){var $thumb=$(this),$btnUpload=$thumb.find('.kv-file-upload'),$btnDelete=$thumb.find('.kv-file-remove');if($thumb.hasClass('file-preview-loading')){self._setThumbStatus($thumb,'New');$thumb.removeClass('file-uploading');}$btnUpload.removeAttr('disabled');$btnDelete.removeAttr('disabled');});self._setProgressCancelled();}};fnSuccess=function(data,textStatus,jqXHR){var outData=self._getOutData(formdata,jqXHR,data),key=0,$thumbs=self._getThumbs(':not(.file-preview-success)'),keys=$h.isEmpty(data)||$h.isEmpty(data.errorkeys)?[]:data.errorkeys;if($h.isEmpty(data)||$h.isEmpty(data.error)){self._raise('filebatchuploadsuccess',[outData]);setAllUploaded();if(self.showPreview){$thumbs.each(function(){var $thumb=$(this);self._setThumbStatus($thumb,'Success');$thumb.removeClass('file-uploading');$thumb.find('.kv-file-upload').hide().removeAttr('disabled');});self._initUploadSuccess(data);}else{self.reset();}self._setProgress(101);}else{if(self.showPreview){$thumbs.each(function(){var $thumb=$(this);$thumb.removeClass('file-uploading');$thumb.find('.kv-file-upload').removeAttr('disabled');$thumb.find('.kv-file-remove').removeAttr('disabled');if(keys.length===0||$.inArray(key,keys)!==-1){self._setPreviewError($thumb,true);if(!self.retryErrorUploads){$thumb.find('.kv-file-upload').hide();self.fileManager.remove($thumb);}}else{$thumb.find('.kv-file-upload').hide();self._setThumbStatus($thumb,'Success');self.fileManager.remove($thumb);}if(!$thumb.hasClass('file-preview-error')||self.retryErrorUploads){key++;}});self._initUploadSuccess(data);}errMsg=self._parseError(op,jqXHR,self.msgUploadError);self._showFileError(errMsg,outData,'filebatchuploaderror');self._setProgress(101,self.$progress,self.msgUploadError);}};fnComplete=function(){self.unlock();self._initSuccessThumbs();self._clearFileInput();self._raise('filebatchuploadcomplete',[self.fileManager.stack,self._getExtraData()]);};fnError=function(jqXHR,textStatus,errorThrown){var outData=self._getOutData(formdata,jqXHR);errMsg=self._parseError(op,jqXHR,errorThrown);self._showFileError(errMsg,outData,'filebatchuploaderror');self.uploadFileCount=total-1;if(!self.showPreview){return;}self._getThumbs().each(function(){var $thumb=$(this);$thumb.removeClass('file-uploading');if(self.fileManager.getFile($thumb.attr('data-fileid'))){self._setPreviewError($thumb);}});self._getThumbs().removeClass('file-uploading');self._getThumbs(' .kv-file-upload').removeAttr('disabled');self._getThumbs(' .kv-file-delete').removeAttr('disabled');self._setProgress(101,self.$progress,self.msgAjaxProgressError.replace('{operation}',op));};var ctr=0;$.each(self.fileManager.stack,function(key,data){if(!$h.isEmpty(data.file)){formdata.append(self.uploadFileAttr,data.file,(data.nameFmt||('untitled_'+ctr)));}ctr++;});self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,formdata);},_uploadExtraOnly:function(){var self=this,params={},fnBefore,fnSuccess,fnComplete,fnError,formdata=new FormData(),errMsg,op=self.ajaxOperations.uploadExtra;if(self._abort(params)){return;}fnBefore=function(jqXHR){self.lock();var outData=self._getOutData(formdata,jqXHR);self._raise('filebatchpreupload',[outData]);self._setProgress(50);params.data=outData;params.xhr=jqXHR;if(self._abort(params)){jqXHR.abort();self._setProgressCancelled();}};fnSuccess=function(data,textStatus,jqXHR){var outData=self._getOutData(formdata,jqXHR,data);if($h.isEmpty(data)||$h.isEmpty(data.error)){self._raise('filebatchuploadsuccess',[outData]);self._clearFileInput();self._initUploadSuccess(data);self._setProgress(101);}else{errMsg=self._parseError(op,jqXHR,self.msgUploadError);self._showFileError(errMsg,outData,'filebatchuploaderror');}};fnComplete=function(){self.unlock();self._clearFileInput();self._raise('filebatchuploadcomplete',[self.fileManager.stack,self._getExtraData()]);};fnError=function(jqXHR,textStatus,errorThrown){var outData=self._getOutData(formdata,jqXHR);errMsg=self._parseError(op,jqXHR,errorThrown);params.data=outData;self._showFileError(errMsg,outData,'filebatchuploaderror');self._setProgress(101,self.$progress,self.msgAjaxProgressError.replace('{operation}',op));};self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,formdata);},_deleteFileIndex:function($frame){var self=this,ind=$frame.attr('data-fileindex'),rev=self.reversePreviewOrder;if(ind.substring(0,5)==='init_'){ind=parseInt(ind.replace('init_',''));self.initialPreview=$h.spliceArray(self.initialPreview,ind,rev);self.initialPreviewConfig=$h.spliceArray(self.initialPreviewConfig,ind,rev);self.initialPreviewThumbTags=$h.spliceArray(self.initialPreviewThumbTags,ind,rev);self.getFrames().each(function(){var $nFrame=$(this),nInd=$nFrame.attr('data-fileindex');if(nInd.substring(0,5)==='init_'){nInd=parseInt(nInd.replace('init_',''));if(nInd>ind){nInd--;$nFrame.attr('data-fileindex','init_'+nInd);}}});if(self.uploadAsync||self.enableResumableUpload){self.cacheInitialPreview=self.getPreview();}}},_initFileActions:function(){var self=this,$preview=self.$preview;if(!self.showPreview){return;}self._initZoomButton();self.getFrames(' .kv-file-remove').each(function(){var $el=$(this),$frame=$el.closest($h.FRAMES),hasError,id=$frame.attr('id'),ind=$frame.attr('data-fileindex'),n,cap,status;self._handler($el,'click',function(){status=self._raise('filepreremove',[id,ind]);if(status===false||!self._validateMinCount()){return false;}hasError=$frame.hasClass('file-preview-error');$h.cleanMemory($frame);$frame.fadeOut('slow',function(){$h.cleanZoomCache($preview.find('#zoom-'+id));self.fileManager.remove($frame);self._clearObjects($frame);$frame.remove();if(id&&hasError){self.$errorContainer.find('li[data-thumb-id="'+id+'"]').fadeOut('fast',function(){$(this).remove();if(!self._errorsExist()){self._resetErrors();}});}self._clearFileInput();var chk=self.previewCache.count(true),len=self.fileManager.count(),file,hasThumb=self.showPreview&&self.getFrames().length;if(len===0&&chk===0&&!hasThumb){self.reset();}else{n=chk+len;if(n>1){cap=self._getMsgSelected(n);}else{file=self.fileManager.getFirstFile();cap=file?file.nameFmt:'_';}self._setCaption(cap);}self._raise('fileremoved',[id,ind]);});});});self.getFrames(' .kv-file-upload').each(function(){var $el=$(this);self._handler($el,'click',function(){var $frame=$el.closest($h.FRAMES),id=$frame.attr('data-fileid');self.$progress.hide();if($frame.hasClass('file-preview-error')&&!self.retryErrorUploads){return;}self._uploadSingle(self.fileManager.getIndex(id),id,false);});});},_initPreviewActions:function(){var self=this,$preview=self.$preview,deleteExtraData=self.deleteExtraData||{},btnRemove=$h.FRAMES+' .kv-file-remove',settings=self.fileActionSettings,origClass=settings.removeClass,errClass=settings.removeErrorClass,resetProgress=function(){var hasFiles=self.isAjaxUpload?self.previewCache.count(true):self._inputFileCount();if(!self.getFrames().length&&!hasFiles){self._setCaption('');self.reset();self.initialCaption='';}};self._initZoomButton();$preview.find(btnRemove).each(function(){var $el=$(this),vUrl=$el.data('url')||self.deleteUrl,vKey=$el.data('key'),errMsg,fnBefore,fnSuccess,fnError,op=self.ajaxOperations.deleteThumb;if($h.isEmpty(vUrl)||vKey===undefined){return;}if(typeof vUrl==='function'){vUrl=vUrl();}var $frame=$el.closest($h.FRAMES),cache=self.previewCache.data,settings,params,config,fileName,extraData,index=$frame.attr('data-fileindex');index=parseInt(index.replace('init_',''));config=$h.isEmpty(cache.config)&&$h.isEmpty(cache.config[index])?null:cache.config[index];extraData=$h.isEmpty(config)||$h.isEmpty(config.extra)?deleteExtraData:config.extra;fileName=config.filename||config.caption||'';if(typeof extraData==='function'){extraData=extraData();}params={id:$el.attr('id'),key:vKey,extra:extraData};fnBefore=function(jqXHR){self.ajaxAborted=false;self._raise('filepredelete',[vKey,jqXHR,extraData]);if(self._abort()){jqXHR.abort();}else{$el.removeClass(errClass);$h.addCss($frame,'file-uploading');$h.addCss($el,'disabled '+origClass);}};fnSuccess=function(data,textStatus,jqXHR){var n,cap;if(!$h.isEmpty(data)&&!$h.isEmpty(data.error)){params.jqXHR=jqXHR;params.response=data;errMsg=self._parseError(op,jqXHR,self.msgDeleteError,fileName);self._showFileError(errMsg,params,'filedeleteerror');$frame.removeClass('file-uploading');$el.removeClass('disabled '+origClass).addClass(errClass);resetProgress();return;}$frame.removeClass('file-uploading').addClass('file-deleted');$frame.fadeOut('slow',function(){index=parseInt(($frame.attr('data-fileindex')).replace('init_',''));self.previewCache.unset(index);self._deleteFileIndex($frame);n=self.previewCache.count(true);cap=n>0?self._getMsgSelected(n):'';self._setCaption(cap);self._raise('filedeleted',[vKey,jqXHR,extraData]);$h.cleanZoomCache($preview.find('#zoom-'+$frame.attr('id')));self._clearObjects($frame);$frame.remove();resetProgress();});};fnError=function(jqXHR,textStatus,errorThrown){var errMsg=self._parseError(op,jqXHR,errorThrown,fileName);params.jqXHR=jqXHR;params.response={};self._showFileError(errMsg,params,'filedeleteerror');$frame.removeClass('file-uploading');$el.removeClass('disabled '+origClass).addClass(errClass);resetProgress();};self._initAjaxSettings();self._mergeAjaxCallback('beforeSend',fnBefore,'delete');self._mergeAjaxCallback('success',fnSuccess,'delete');self._mergeAjaxCallback('error',fnError,'delete');settings=$.extend(true,{},{url:self._encodeURI(vUrl),type:'POST',dataType:'json',data:$.extend(true,{},{key:vKey},extraData)},self._ajaxDeleteSettings);self._handler($el,'click',function(){if(!self._validateMinCount()){return false;}self.ajaxAborted=false;self._raise('filebeforedelete',[vKey,extraData]);if(self.ajaxAborted instanceof Promise){self.ajaxAborted.then(function(result){if(!result){$.ajax(settings);}});}else{if(!self.ajaxAborted){$.ajax(settings);}}});});},_hideFileIcon:function(){var self=this;if(self.overwriteInitial){self.$captionContainer.removeClass('icon-visible');}},_showFileIcon:function(){var self=this;$h.addCss(self.$captionContainer,'icon-visible');},_getSize:function(bytes,sizes){var self=this,size=parseFloat(bytes),i,func=self.fileSizeGetter,out;if(!$.isNumeric(bytes)||!$.isNumeric(size)){return'';}if(typeof func==='function'){out=func(size);}else{if(size===0){out='0.00 B';}else{i=Math.floor(Math.log(size)/Math.log(1024));if(!sizes){sizes=['B','KB','MB','GB','TB','PB','EB','ZB','YB'];}out=(size/Math.pow(1024,i)).toFixed(2)*1+' '+sizes[i];}}return self._getLayoutTemplate('size').replace('{sizeText}',out);},_getFileType:function(ftype){var self=this;return self.mimeTypeAliases[ftype]||ftype;},_generatePreviewTemplate:function(cat,data,fname,ftype,previewId,fileId,isError,size,frameClass,foot,ind,templ,attrs,zoomData){var self=this,caption=self.slug(fname),prevContent,zoomContent='',styleAttribs='',screenW=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,config,newCat=self.preferIconicPreview?'other':cat,title=caption,alt=caption,footer=foot||self._renderFileFooter(cat,caption,size,'auto',isError),hasIconSetting=self._getPreviewIcon(fname),typeCss='type-default',forcePrevIcon=hasIconSetting&&self.preferIconicPreview,forceZoomIcon=hasIconSetting&&self.preferIconicZoomPreview,getContent;config=screenW<400?(self.previewSettingsSmall[newCat]||self.defaults.previewSettingsSmall[newCat]):(self.previewSettings[newCat]||self.defaults.previewSettings[newCat]);if(config){$.each(config,function(key,val){styleAttribs+=key+':'+val+';';});}getContent=function(c,d,zoom,frameCss){var id=zoom?'zoom-'+previewId:previewId,tmplt=self._getPreviewTemplate(c),css=(frameClass||'')+' '+frameCss;if(self.frameClass){css=self.frameClass+' '+css;}if(zoom){css=css.replace(' '+$h.SORT_CSS,'');}tmplt=self._parseFilePreviewIcon(tmplt,fname);if(c==='text'){d=$h.htmlEncode(d);}if(cat==='object'&&!ftype){$.each(self.defaults.fileTypeSettings,function(key,func){if(key==='object'||key==='other'){return;}if(func(fname,ftype)){typeCss='type-'+key;}});}if(!$h.isEmpty(attrs)){if(attrs.title!==undefined&&attrs.title!==null){title=attrs.title;}if(attrs.alt!==undefined&&attrs.alt!==null){title=attrs.alt;}}return tmplt.setTokens({'previewId':id,'caption':caption,'title':title,'alt':alt,'frameClass':css,'type':self._getFileType(ftype),'fileindex':ind,'fileid':fileId||'','typeCss':typeCss,'footer':footer,'data':d,'template':templ||cat,'style':styleAttribs?'style="'+styleAttribs+'"':''});};ind=ind||previewId.slice(previewId.lastIndexOf('-')+1);if(self.fileActionSettings.showZoom){zoomContent=getContent((forceZoomIcon?'other':cat),zoomData?zoomData:data,true,'kv-zoom-thumb');}zoomContent='\n'+self._getLayoutTemplate('zoomCache').replace('{zoomContent}',zoomContent);if(typeof self.sanitizeZoomCache==='function'){zoomContent=self.sanitizeZoomCache(zoomContent);}prevContent=getContent((forcePrevIcon?'other':cat),data,false,'kv-preview-thumb');return prevContent+zoomContent;},_addToPreview:function($preview,content){var self=this;return self.reversePreviewOrder?$preview.prepend(content):$preview.append(content);},_previewDefault:function(file,previewId,isDisabled){var self=this,$preview=self.$preview;if(!self.showPreview){return;}var fname=$h.getFileName(file),ftype=file?file.type:'',content,size=file.size||0,caption=self._getFileName(file,''),isError=isDisabled===true&&!self.isAjaxUpload,data=$h.createObjectURL(file),fileId=self.fileManager.getId(file);self._clearDefaultPreview();content=self._generatePreviewTemplate('other',data,fname,ftype,previewId,fileId,isError,size);self._addToPreview($preview,content);self._setThumbAttr(previewId,caption,size);if(isDisabled===true&&self.isAjaxUpload){self._setThumbStatus($('#'+previewId),'Error');}},canPreview:function(file){var self=this;if(!file||!self.showPreview||!self.$preview||!self.$preview.length){return false;}var name=file.name||'',type=file.type||'',size=(file.size||0)/1000,cat=self._parseFileType(type,name),allowedTypes,allowedMimes,allowedExts,skipPreview,types=self.allowedPreviewTypes,mimes=self.allowedPreviewMimeTypes,exts=self.allowedPreviewExtensions||[],dTypes=self.disabledPreviewTypes,dMimes=self.disabledPreviewMimeTypes,dExts=self.disabledPreviewExtensions||[],maxSize=self.maxFilePreviewSize&&parseFloat(self.maxFilePreviewSize)||0,expAllExt=new RegExp('\\.('+exts.join('|')+')$','i'),expDisExt=new RegExp('\\.('+dExts.join('|')+')$','i');allowedTypes=!types||types.indexOf(cat)!==-1;allowedMimes=!mimes||mimes.indexOf(type)!==-1;allowedExts=!exts.length||$h.compare(name,expAllExt);skipPreview=(dTypes&&dTypes.indexOf(cat)!==-1)||(dMimes&&dMimes.indexOf(type)!==-1)||(dExts.length&&$h.compare(name,expDisExt))||(maxSize&&!isNaN(maxSize)&&size>maxSize);return!skipPreview&&(allowedTypes||allowedMimes||allowedExts);},_previewFile:function(i,file,theFile,previewId,data,fileInfo){if(!this.showPreview){return;}var self=this,fname=$h.getFileName(file),ftype=fileInfo.type,caption=fileInfo.name,cat=self._parseFileType(ftype,fname),content,$preview=self.$preview,fsize=file.size||0,iData=(cat==='text'||cat==='html'||cat==='image')?theFile.target.result:data,fileId=self.fileManager.getId(file);if(cat==='html'&&self.purifyHtml&&window.DOMPurify){iData=window.DOMPurify.sanitize(iData);}content=self._generatePreviewTemplate(cat,iData,fname,ftype,previewId,fileId,false,fsize);self._clearDefaultPreview();self._addToPreview($preview,content);var $thumb=$preview.find('#'+previewId),$img=$thumb.find('img'),id=$thumb.attr('data-fileid');self._validateImageOrientation($img,file,previewId,id,caption,ftype,fsize,iData);self._setThumbAttr(previewId,caption,fsize);self._initSortable();},_setThumbAttr:function(id,caption,size){var self=this,$frame=$('#'+id);if($frame.length){size=size&&size>0?self._getSize(size):'';$frame.data({'caption':caption,'size':size});}},_setInitThumbAttr:function(){var self=this,data=self.previewCache.data,len=self.previewCache.count(true),config,caption,size,previewId;if(len===0){return;}for(var i=0;i<len;i++){config=data.config[i];previewId=self.previewInitId+'-'+'init_'+i;caption=$h.ifSet('caption',config,$h.ifSet('filename',config));size=$h.ifSet('size',config);self._setThumbAttr(previewId,caption,size);}},_slugDefault:function(text){return $h.isEmpty(text)?'':String(text).replace(/[\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g,'_');},_updateFileDetails:function(numFiles){var self=this,$el=self.$element,label,n,log,nFiles,file,name=($h.isIE(9)&&$h.findFileName($el.val()))||($el[0].files[0]&&$el[0].files[0].name);if(!name&&self.fileManager.count()>0){file=self.fileManager.getFirstFile();label=file.nameFmt;}else{label=name?self.slug(name):'_';}n=self.isAjaxUpload?self.fileManager.count():numFiles;nFiles=self.previewCache.count(true)+n;log=n===1?label:self._getMsgSelected(nFiles);if(self.isError){self.$previewContainer.removeClass('file-thumb-loading');self.$previewStatus.html('');self.$captionContainer.removeClass('icon-visible');}else{self._showFileIcon();}self._setCaption(log,self.isError);self.$container.removeClass('file-input-new file-input-ajax-new');if(arguments.length===1){self._raise('fileselect',[numFiles,label]);}if(self.previewCache.count(true)){self._initPreviewActions();}},_setThumbStatus:function($thumb,status){var self=this;if(!self.showPreview){return;}var icon='indicator'+status,msg=icon+'Title',css='file-preview-'+status.toLowerCase(),$indicator=$thumb.find('.file-upload-indicator'),config=self.fileActionSettings;$thumb.removeClass('file-preview-success file-preview-error file-preview-paused file-preview-loading');if(status==='Success'){$thumb.find('.file-drag-handle').remove();}$indicator.html(config[icon]);$indicator.attr('title',config[msg]);$thumb.addClass(css);if(status==='Error'&&!self.retryErrorUploads){$thumb.find('.kv-file-upload').attr('disabled',true);}},_setProgressCancelled:function(){var self=this;self._setProgress(101,self.$progress,self.msgCancelled);},_setProgress:function(p,$el,error,stats){var self=this;$el=$el||self.$progress;if(!$el.length){return;}var pct=Math.min(p,100),out,pctLimit=self.progressUploadThreshold,t=p<=100?self.progressTemplate:self.progressCompleteTemplate,template=pct<100?self.progressTemplate:(error?(self.paused?self.progressPauseTemplate:self.progressErrorTemplate):t);if(p>=100){stats='';}if(!$h.isEmpty(template)){if(pctLimit&&pct>pctLimit&&p<=100){out=template.setTokens({'percent':pctLimit,'status':self.msgUploadThreshold});}else{out=template.setTokens({'percent':pct,'status':(p>100?self.msgUploadEnd:pct+'%')});}stats=stats||'';out=out.setTokens({stats:stats});$el.html(out);if(error){$el.find('[role="progressbar"]').html(error);}}},_setFileDropZoneTitle:function(){var self=this,$zone=self.$container.find('.file-drop-zone'),title=self.dropZoneTitle,strFiles;if(self.isClickable){strFiles=$h.isEmpty(self.$element.attr('multiple'))?self.fileSingle:self.filePlural;title+=self.dropZoneClickTitle.replace('{files}',strFiles);}$zone.find('.'+self.dropZoneTitleClass).remove();if(!self.showPreview||$zone.length===0||self.fileManager.count()>0||!self.dropZoneEnabled||(!self.isAjaxUpload&&self.$element.files)){return;}if($zone.find($h.FRAMES).length===0&&$h.isEmpty(self.defaultPreviewContent)){$zone.prepend('<div class="'+self.dropZoneTitleClass+'">'+title+'</div>');}self.$container.removeClass('file-input-new');$h.addCss(self.$container,'file-input-ajax-new');},_getStats:function(stats){var self=this,pendingTime,t;if(!self.showUploadStats||!stats||!stats.bitrate){return'';}t=self._getLayoutTemplate('stats');pendingTime=(!stats.elapsed||!stats.bps)?self.msgCalculatingTime:self.msgPendingTime.setTokens({time:$h.getElapsed(Math.ceil(stats.pendingBytes/stats.bps))});return t.setTokens({uploadSpeed:stats.bitrate,pendingTime:pendingTime});},_setResumableProgress:function(pct,stats,$thumb){var self=this,rm=self.resumableManager,obj=$thumb?rm:self,$prog=$thumb?$thumb.find('.file-thumb-progress'):null;if(obj.lastProgress===0){obj.lastProgress=pct;}if(pct<obj.lastProgress){pct=obj.lastProgress;}self._setProgress(pct,$prog,null,self._getStats(stats));obj.lastProgress=pct;},_setFileUploadStats:function(id,pct,total,stats){var self=this,$prog=self.$progress;if(!self.showPreview&&(!$prog||!$prog.length)){return;}var fm=self.fileManager,$thumb=fm.getThumb(id),pctTot,rm=self.resumableManager,totUpSize=0,totSize=fm.getTotalSize(),totStats=$.extend(true,{},stats);if(self.enableResumableUpload){var loaded=stats.loaded,currUplSize=rm.getUploadedSize(),currTotSize=rm.file.size,totLoaded;loaded+=currUplSize;totLoaded=fm.uploadedSize+loaded;pct=$h.round(100*loaded/currTotSize);stats.pendingBytes=currTotSize-currUplSize;self._setResumableProgress(pct,stats,$thumb);pctTot=Math.floor(100*totLoaded/totSize);totStats.pendingBytes=totSize-totLoaded;self._setResumableProgress(pctTot,totStats);}else{fm.setProgress(id,pct);$prog=$thumb&&$thumb.length?$thumb.find('.file-thumb-progress'):null;self._setProgress(pct,$prog,null,self._getStats(stats));$.each(fm.stats,function(id,cfg){totUpSize+=cfg.loaded;});totStats.pendingBytes=totSize-totUpSize;pctTot=$h.round(totUpSize/totSize*100);self._setProgress(pctTot,null,null,self._getStats(totStats));}},_validateMinCount:function(){var self=this,len=self.isAjaxUpload?self.fileManager.count():self._inputFileCount();if(self.validateInitialCount&&self.minFileCount>0&&self._getFileCount(len-1)<self.minFileCount){self._noFilesError({});return false;}return true;},_getFileCount:function(fileCount){var self=this,addCount=0;if(self.validateInitialCount&&!self.overwriteInitial){addCount=self.previewCache.count(true);fileCount+=addCount;}return fileCount;},_getFileId:function(file){return $h.getFileId(file,this.generateFileId);},_getFileName:function(file,defaultValue){var self=this,fileName=$h.getFileName(file);return fileName?self.slug(fileName):defaultValue;},_getFileNames:function(skipNull){var self=this;return self.filenames.filter(function(n){return(skipNull?n!==undefined:n!==undefined&&n!==null);});},_setPreviewError:function($thumb,keepFile){var self=this,removeFrame=self.removeFromPreviewOnError&&!self.retryErrorUploads;if(!keepFile||removeFrame){self.fileManager.remove($thumb);}if(!self.showPreview){return;}if(removeFrame){$thumb.remove();return;}else{self._setThumbStatus($thumb,'Error');}self._refreshUploadButton($thumb);},_refreshUploadButton:function($thumb){var self=this,$btn=$thumb.find('.kv-file-upload'),cfg=self.fileActionSettings,icon=cfg.uploadIcon,title=cfg.uploadTitle;if(!$btn.length){return;}if(self.retryErrorUploads){icon=cfg.uploadRetryIcon;title=cfg.uploadRetryTitle;}$btn.attr('title',title).html(icon);},_checkDimensions:function(i,chk,$img,$thumb,fname,type,params){var self=this,msg,dim,tag=chk==='Small'?'min':'max',limit=self[tag+'Image'+type],$imgEl,isValid;if($h.isEmpty(limit)||!$img.length){return;}$imgEl=$img[0];dim=(type==='Width')?$imgEl.naturalWidth||$imgEl.width:$imgEl.naturalHeight||$imgEl.height;isValid=chk==='Small'?dim>=limit:dim<=limit;if(isValid){return;}msg=self['msgImage'+type+chk].setTokens({'name':fname,'size':limit});self._showFileError(msg,params);self._setPreviewError($thumb);},_getExifObj:function(data){var self=this,exifObj=null,error=$h.logMessages.exifWarning;if(data.slice(0,23)!=='data:image/jpeg;base64,'&&data.slice(0,22)!=='data:image/jpg;base64,'){exifObj=null;return;}try{exifObj=window.piexif?window.piexif.load(data):null;}catch(err){exifObj=null;error=err&&err.message||'';}if(!exifObj){self._log($h.logMessages.badExifParser,{details:error});}return exifObj;},setImageOrientation:function($img,$zoomImg,value,$thumb){var self=this,invalidImg=!$img||!$img.length,invalidZoomImg=!$zoomImg||!$zoomImg.length,$mark,isHidden=false,$div,zoomOnly=invalidImg&&$thumb&&$thumb.attr('data-template')==='image',ev;if(invalidImg&&invalidZoomImg){return;}ev='load.fileinputimageorient';if(zoomOnly){$img=$zoomImg;$zoomImg=null;$img.css(self.previewSettings.image);$div=$(document.createElement('div')).appendTo($thumb.find('.kv-file-content'));$mark=$(document.createElement('span')).insertBefore($img);$img.css('visibility','hidden').removeClass('file-zoom-detail').appendTo($div);}else{isHidden=!$img.is(':visible');}$img.off(ev).on(ev,function(){if(isHidden){self.$preview.removeClass('hide-content');$thumb.find('.kv-file-content').css('visibility','hidden');}var img=$img.get(0),zoomImg=$zoomImg&&$zoomImg.length?$zoomImg.get(0):null,h=img.offsetHeight,w=img.offsetWidth,r=$h.getRotation(value);if(isHidden){$thumb.find('.kv-file-content').css('visibility','visible');self.$preview.addClass('hide-content');}$img.data('orientation',value);if(zoomImg){$zoomImg.data('orientation',value);}if(value<5){$h.setTransform(img,r);$h.setTransform(zoomImg,r);return;}var offsetAngle=Math.atan(w/h),origFactor=Math.sqrt(Math.pow(h,2)+Math.pow(w,2)),scale=!origFactor?1:(h/Math.cos(Math.PI/2+offsetAngle))/origFactor,s=' scale('+Math.abs(scale)+')';$h.setTransform(img,r+s);$h.setTransform(zoomImg,r+s);if(zoomOnly){$img.css('visibility','visible').insertAfter($mark).addClass('file-zoom-detail');$mark.remove();$div.remove();}});},_validateImageOrientation:function($img,file,previewId,fileId,caption,ftype,fsize,iData){var self=this,exifObj,value,autoOrientImage=self.autoOrientImage;exifObj=autoOrientImage?self._getExifObj(iData):null;value=exifObj?exifObj['0th'][piexif.ImageIFD.Orientation]:null;if(!value){self._validateImage(previewId,fileId,caption,ftype,fsize,iData,exifObj);return;}self.setImageOrientation($img,$('#zoom-'+previewId+' img'),value,$('#'+previewId));self._raise('fileimageoriented',{'$img':$img,'file':file});self._validateImage(previewId,fileId,caption,ftype,fsize,iData,exifObj);},_validateImage:function(previewId,fileId,fname,ftype,fsize,iData,exifObj){var self=this,$preview=self.$preview,params,w1,w2,$thumb=$preview.find('#'+previewId),i=$thumb.attr('data-fileindex'),$img=$thumb.find('img');fname=fname||'Untitled';$img.one('load',function(){w1=$thumb.width();w2=$preview.width();if(w1>w2){$img.css('width','100%');}params={ind:i,id:previewId,fileId:fileId};self._checkDimensions(i,'Small',$img,$thumb,fname,'Width',params);self._checkDimensions(i,'Small',$img,$thumb,fname,'Height',params);if(!self.resizeImage){self._checkDimensions(i,'Large',$img,$thumb,fname,'Width',params);self._checkDimensions(i,'Large',$img,$thumb,fname,'Height',params);}self._raise('fileimageloaded',[previewId]);self.fileManager.addImage(fileId,{ind:i,img:$img,thumb:$thumb,pid:previewId,typ:ftype,siz:fsize,validated:false,imgData:iData,exifObj:exifObj});$thumb.data('exif',exifObj);self._validateAllImages();}).one('error',function(){self._raise('fileimageloaderror',[previewId]);}).each(function(){if(this.complete){$(this).trigger('load');}else{if(this.error){$(this).trigger('error');}}});},_validateAllImages:function(){var self=this,counter={val:0},numImgs=self.fileManager.getImageCount(),fsize,minSize=self.resizeIfSizeMoreThan;if(numImgs!==self.fileManager.totalImages){return;}self._raise('fileimagesloaded');if(!self.resizeImage){return;}$.each(self.fileManager.loadedImages,function(id,config){if(!config.validated){fsize=config.siz;if(fsize&&fsize>minSize*1000){self._getResizedImage(id,config,counter,numImgs);}config.validated=true;}});},_getResizedImage:function(id,config,counter,numImgs){var self=this,img=$(config.img)[0],width=img.naturalWidth,height=img.naturalHeight,blob,ratio=1,maxWidth=self.maxImageWidth||width,maxHeight=self.maxImageHeight||height,isValidImage=!!(width&&height),chkWidth,chkHeight,canvas=self.imageCanvas,dataURI,context=self.imageCanvasContext,type=config.typ,pid=config.pid,ind=config.ind,$thumb=config.thumb,throwError,msg,exifObj=config.exifObj,exifStr,file,params,evParams;throwError=function(msg,params,ev){if(self.isAjaxUpload){self._showFileError(msg,params,ev);}else{self._showError(msg,params,ev);}self._setPreviewError($thumb);};file=self.fileManager.getFile(id);params={id:pid,'index':ind,fileId:id};evParams=[id,pid,ind];if(!file||!isValidImage||(width<=maxWidth&&height<=maxHeight)){if(isValidImage&&file){self._raise('fileimageresized',evParams);}counter.val++;if(counter.val===numImgs){self._raise('fileimagesresized');}if(!isValidImage){throwError(self.msgImageResizeError,params,'fileimageresizeerror');return;}}type=type||self.resizeDefaultImageType;chkWidth=width>maxWidth;chkHeight=height>maxHeight;if(self.resizePreference==='width'){ratio=chkWidth?maxWidth/width:(chkHeight?maxHeight/height:1);}else{ratio=chkHeight?maxHeight/height:(chkWidth?maxWidth/width:1);}self._resetCanvas();width*=ratio;height*=ratio;canvas.width=width;canvas.height=height;try{context.drawImage(img,0,0,width,height);dataURI=canvas.toDataURL(type,self.resizeQuality);if(exifObj){exifStr=window.piexif.dump(exifObj);dataURI=window.piexif.insert(exifStr,dataURI);}blob=$h.dataURI2Blob(dataURI);self.fileManager.setFile(id,blob);self._raise('fileimageresized',evParams);counter.val++;if(counter.val===numImgs){self._raise('fileimagesresized',[undefined,undefined]);}if(!(blob instanceof Blob)){throwError(self.msgImageResizeError,params,'fileimageresizeerror');}}catch(err){counter.val++;if(counter.val===numImgs){self._raise('fileimagesresized',[undefined,undefined]);}msg=self.msgImageResizeException.replace('{errors}',err.message);throwError(msg,params,'fileimageresizeexception');}},_initBrowse:function($container){var self=this,$el=self.$element;if(self.showBrowse){self.$btnFile=$container.find('.btn-file').append($el);}else{$el.appendTo($container).attr('tabindex',-1);$h.addCss($el,'file-no-browse');}},_initClickable:function(){var self=this,$zone,$tmpZone;if(!self.isClickable){return;}$zone=self.$dropZone;if(!self.isAjaxUpload){$tmpZone=self.$preview.find('.file-default-preview');if($tmpZone.length){$zone=$tmpZone;}}$h.addCss($zone,'clickable');$zone.attr('tabindex',-1);self._handler($zone,'click',function(e){var $tar=$(e.target);if(!$(self.elErrorContainer+':visible').length&&(!$tar.parents('.file-preview-thumbnails').length||$tar.parents('.file-default-preview').length)){self.$element.data('zoneClicked',true).trigger('click');$zone.blur();}});},_initCaption:function(){var self=this,cap=self.initialCaption||'';if(self.overwriteInitial||$h.isEmpty(cap)){self.$caption.val('');return false;}self._setCaption(cap);return true;},_setCaption:function(content,isError){var self=this,title,out,icon,n,cap,file;if(!self.$caption.length){return;}self.$captionContainer.removeClass('icon-visible');if(isError){title=$('<div>'+self.msgValidationError+'</div>').text();n=self.fileManager.count();if(n){file=self.fileManager.getFirstFile();cap=n===1&&file?file.nameFmt:self._getMsgSelected(n);}else{cap=self._getMsgSelected(self.msgNo);}out=$h.isEmpty(content)?cap:content;icon='<span class="'+self.msgValidationErrorClass+'">'+self.msgValidationErrorIcon+'</span>';}else{if($h.isEmpty(content)){return;}title=$('<div>'+content+'</div>').text();out=title;icon=self._getLayoutTemplate('fileIcon');}self.$captionContainer.addClass('icon-visible');self.$caption.attr('title',title).val(out);self.$captionIcon.html(icon);},_createContainer:function(){var self=this,attribs={'class':'file-input file-input-new'+(self.rtl?' kv-rtl':'')},$container=$(document.createElement('div')).attr(attribs).html(self._renderMain());$container.insertBefore(self.$element);self._initBrowse($container);if(self.theme){$container.addClass('theme-'+self.theme);}return $container;},_refreshContainer:function(){var self=this,$container=self.$container,$el=self.$element;$el.insertAfter($container);$container.html(self._renderMain());self._initBrowse($container);self._validateDisabled();},_validateDisabled:function(){var self=this;self.$caption.attr({readonly:self.isDisabled});},_renderMain:function(){var self=this,dropCss=self.dropZoneEnabled?' file-drop-zone':'file-drop-disabled',close=!self.showClose?'':self._getLayoutTemplate('close'),preview=!self.showPreview?'':self._getLayoutTemplate('preview').setTokens({'class':self.previewClass,'dropClass':dropCss}),css=self.isDisabled?self.captionClass+' file-caption-disabled':self.captionClass,caption=self.captionTemplate.setTokens({'class':css+' kv-fileinput-caption'});return self.mainTemplate.setTokens({'class':self.mainClass+(!self.showBrowse&&self.showCaption?' no-browse':''),'preview':preview,'close':close,'caption':caption,'upload':self._renderButton('upload'),'remove':self._renderButton('remove'),'cancel':self._renderButton('cancel'),'pause':self._renderButton('pause'),'browse':self._renderButton('browse')});},_renderButton:function(type){var self=this,tmplt=self._getLayoutTemplate('btnDefault'),css=self[type+'Class'],title=self[type+'Title'],icon=self[type+'Icon'],label=self[type+'Label'],status=self.isDisabled?' disabled':'',btnType='button';switch(type){case'remove':if(!self.showRemove){return'';}break;case'cancel':if(!self.showCancel){return'';}css+=' kv-hidden';break;case'pause':if(!self.showPause){return'';}css+=' kv-hidden';break;case'upload':if(!self.showUpload){return'';}if(self.isAjaxUpload&&!self.isDisabled){tmplt=self._getLayoutTemplate('btnLink').replace('{href}',self.uploadUrl);}else{btnType='submit';}break;case'browse':if(!self.showBrowse){return'';}tmplt=self._getLayoutTemplate('btnBrowse');break;default:return'';}css+=type==='browse'?' btn-file':' fileinput-'+type+' fileinput-'+type+'-button';if(!$h.isEmpty(label)){label=' <span class="'+self.buttonLabelClass+'">'+label+'</span>';}return tmplt.setTokens({'type':btnType,'css':css,'title':title,'status':status,'icon':icon,'label':label});},_renderThumbProgress:function(){var self=this;return'<div class="file-thumb-progress kv-hidden">'+self.progressInfoTemplate.setTokens({percent:101,status:self.msgUploadBegin,stats:''})+'</div>';},_renderFileFooter:function(cat,caption,size,width,isError){var self=this,config=self.fileActionSettings,rem=config.showRemove,drg=config.showDrag,upl=config.showUpload,zoom=config.showZoom,out,params,template=self._getLayoutTemplate('footer'),tInd=self._getLayoutTemplate('indicator'),ind=isError?config.indicatorError:config.indicatorNew,title=isError?config.indicatorErrorTitle:config.indicatorNewTitle,indicator=tInd.setTokens({'indicator':ind,'indicatorTitle':title});size=self._getSize(size);params={type:cat,caption:caption,size:size,width:width,progress:'',indicator:indicator};if(self.isAjaxUpload){params.progress=self._renderThumbProgress();params.actions=self._renderFileActions(params,upl,false,rem,zoom,drg,false,false,false);}else{params.actions=self._renderFileActions(params,false,false,false,zoom,drg,false,false,false);}out=template.setTokens(params);out=$h.replaceTags(out,self.previewThumbTags);return out;},_renderFileActions:function(cfg,showUpl,showDwn,showDel,showZoom,showDrag,disabled,url,key,isInit,dUrl,dFile){var self=this;if(!cfg.type&&isInit){cfg.type='image';}if(self.enableResumableUpload){showUpl=false;}else{if(typeof showUpl==='function'){showUpl=showUpl(cfg);}}if(typeof showDwn==='function'){showDwn=showDwn(cfg);}if(typeof showDel==='function'){showDel=showDel(cfg);}if(typeof showZoom==='function'){showZoom=showZoom(cfg);}if(typeof showDrag==='function'){showDrag=showDrag(cfg);}if(!showUpl&&!showDwn&&!showDel&&!showZoom&&!showDrag){return'';}var vUrl=url===false?'':' data-url="'+url+'"',btnZoom='',btnDrag='',css,vKey=key===false?'':' data-key="'+key+'"',btnDelete='',btnUpload='',btnDownload='',template=self._getLayoutTemplate('actions'),config=self.fileActionSettings,otherButtons=self.otherActionButtons.setTokens({'dataKey':vKey,'key':key}),removeClass=disabled?config.removeClass+' disabled':config.removeClass;if(showDel){btnDelete=self._getLayoutTemplate('actionDelete').setTokens({'removeClass':removeClass,'removeIcon':config.removeIcon,'removeTitle':config.removeTitle,'dataUrl':vUrl,'dataKey':vKey,'key':key});}if(showUpl){btnUpload=self._getLayoutTemplate('actionUpload').setTokens({'uploadClass':config.uploadClass,'uploadIcon':config.uploadIcon,'uploadTitle':config.uploadTitle});}if(showDwn){btnDownload=self._getLayoutTemplate('actionDownload').setTokens({'downloadClass':config.downloadClass,'downloadIcon':config.downloadIcon,'downloadTitle':config.downloadTitle,'downloadUrl':dUrl||self.initialPreviewDownloadUrl});btnDownload=btnDownload.setTokens({'filename':dFile,'key':key});}if(showZoom){btnZoom=self._getLayoutTemplate('actionZoom').setTokens({'zoomClass':config.zoomClass,'zoomIcon':config.zoomIcon,'zoomTitle':config.zoomTitle});}if(showDrag&&isInit){css='drag-handle-init '+config.dragClass;btnDrag=self._getLayoutTemplate('actionDrag').setTokens({'dragClass':css,'dragTitle':config.dragTitle,'dragIcon':config.dragIcon});}return template.setTokens({'delete':btnDelete,'upload':btnUpload,'download':btnDownload,'zoom':btnZoom,'drag':btnDrag,'other':otherButtons});},_browse:function(e){var self=this;if(e&&e.isDefaultPrevented()||!self._raise('filebrowse')){return;}if(self.isError&&!self.isAjaxUpload){self.clear();}self.$captionContainer.focus();},_change:function(e){var self=this;if(self.changeTriggered){return;}var $el=self.$element,isDragDrop=arguments.length>1,isAjaxUpload=self.isAjaxUpload,tfiles,files=isDragDrop?arguments[1]:$el.get(0).files,total,maxCount=!isAjaxUpload&&$h.isEmpty($el.attr('multiple'))?1:self.maxFileCount,len,ctr=self.fileManager.count(),isSingleUpload=$h.isEmpty($el.attr('multiple')),flagSingle=(isSingleUpload&&ctr>0),throwError=function(mesg,file,previewId,index){var p1=$.extend(true,{},self._getOutData(null,{},{},files),{id:previewId,index:index}),p2={id:previewId,index:index,file:file,files:files};return isAjaxUpload?self._showFileError(mesg,p1):self._showError(mesg,p2);},maxCountCheck=function(n,m){var msg=self.msgFilesTooMany.replace('{m}',m).replace('{n}',n);self.isError=throwError(msg,null,null,null);self.$captionContainer.removeClass('icon-visible');self._setCaption('',true);self.$container.removeClass('file-input-new file-input-ajax-new');};self.reader=null;self._resetUpload();self._hideFileIcon();if(self.dropZoneEnabled){self.$container.find('.file-drop-zone .'+self.dropZoneTitleClass).remove();}if(!isAjaxUpload){if(e.target&&e.target.files===undefined){files=e.target.value?[{name:e.target.value.replace(/^.+\\/,'')}]:[];}else{files=e.target.files||{};}}tfiles=files;if($h.isEmpty(tfiles)||tfiles.length===0){if(!isAjaxUpload){self.clear();}self._raise('fileselectnone');return;}self._resetErrors();len=tfiles.length;total=self._getFileCount(isAjaxUpload?(self.fileManager.count()+len):len);if(maxCount>0&&total>maxCount){if(!self.autoReplace||len>maxCount){maxCountCheck((self.autoReplace&&len>maxCount?len:total),maxCount);return;}if(total>maxCount){self._resetPreviewThumbs(isAjaxUpload);}}else{if(!isAjaxUpload||flagSingle){self._resetPreviewThumbs(false);if(flagSingle){self.clearFileStack();}}else{if(isAjaxUpload&&ctr===0&&(!self.previewCache.count(true)||self.overwriteInitial)){self._resetPreviewThumbs(true);}}}self.readFiles(tfiles);},_abort:function(params){var self=this,data;if(self.ajaxAborted&&typeof self.ajaxAborted==='object'&&self.ajaxAborted.message!==undefined){data=$.extend(true,{},self._getOutData(null),params);data.abortData=self.ajaxAborted.data||{};data.abortMessage=self.ajaxAborted.message;self._setProgress(101,self.$progress,self.msgCancelled);self._showFileError(self.ajaxAborted.message,data,'filecustomerror');self.cancel();return true;}return!!self.ajaxAborted;},_resetFileStack:function(){var self=this,i=0;self._getThumbs().each(function(){var $thumb=$(this),ind=$thumb.attr('data-fileindex'),pid=$thumb.attr('id');if(ind==='-1'||ind===-1){return;}if(!self.fileManager.getFile($thumb.attr('data-fileid'))){$thumb.attr({'id':self.previewInitId+'-'+i,'data-fileindex':i});i++;}else{$thumb.attr({'id':'uploaded-'+$h.uniqId(),'data-fileindex':'-1'});}self.$preview.find('#zoom-'+pid).attr({'id':'zoom-'+$thumb.attr('id'),'data-fileindex':$thumb.attr('data-fileindex')});});},_isFileSelectionValid:function(cnt){var self=this;cnt=cnt||0;if(self.required&&!self.getFilesCount()){self.$errorContainer.html('');self._showFileError(self.msgFileRequired);return false;}if(self.minFileCount>0&&self._getFileCount(cnt)<self.minFileCount){self._noFilesError({});return false;}return true;},clearFileStack:function(){var self=this;self.fileManager.clear();self._initResumableUpload();if(self.enableResumableUpload){if(self.showPause===null){self.showPause=true;}if(self.showCancel===null){self.showCancel=false;}}else{self.showPause=false;if(self.showCancel===null){self.showCancel=true;}}return self.$element;},getFileStack:function(){return this.fileManager.stack;},getFileList:function(){return this.fileManager.list();},getFilesCount:function(){var self=this,len=self.isAjaxUpload?self.fileManager.count():self._inputFileCount();return self._getFileCount(len);},readFiles:function(files){this.reader=new FileReader();var self=this,$el=self.$element,reader=self.reader,$container=self.$previewContainer,$status=self.$previewStatus,msgLoading=self.msgLoading,msgProgress=self.msgProgress,previewInitId=self.previewInitId,numFiles=files.length,settings=self.fileTypeSettings,ctr=self.fileManager.count(),readFile,fileTypes=self.allowedFileTypes,typLen=fileTypes?fileTypes.length:0,fileExt=self.allowedFileExtensions,strExt=$h.isEmpty(fileExt)?'':fileExt.join(', '),throwError=function(msg,file,previewId,index,fileId){var p1=$.extend(true,{},self._getOutData(null,{},{},files),{id:previewId,index:index,fileId:fileId}),$thumb=$('#'+previewId),p2={id:previewId,index:index,fileId:fileId,file:file,files:files};self._previewDefault(file,previewId,true);if(self.isAjaxUpload){setTimeout(function(){readFile(index+1);},self.processDelay);}else{numFiles=0;}self._initFileActions();$thumb.remove();self.isError=self.isAjaxUpload?self._showFileError(msg,p1):self._showError(msg,p2);self._updateFileDetails(numFiles);};self.fileManager.clearImages();$.each(files,function(key,file){var func=self.fileTypeSettings.image;if(func&&func(file.type)){self.fileManager.totalImages++;}});readFile=function(i){if($h.isEmpty($el.attr('multiple'))){numFiles=1;}if(i>=numFiles){if(self.isAjaxUpload&&self.fileManager.count()>0){self._raise('filebatchselected',[self.fileManager.stack]);}else{self._raise('filebatchselected',[files]);}$container.removeClass('file-thumb-loading');$status.html('');return;}var node=ctr+i,previewId=previewInitId+'-'+node,file=files[i],fSizeKB,j,msg,$thumb,fnText=settings.text,fnImage=settings.image,fnHtml=settings.html,typ,chk,typ1,typ2,caption=self._getFileName(file,''),fileSize=(file&&file.size||0)/1000,fileExtExpr='',previewData=$h.createObjectURL(file),fileCount=0,strTypes='',fileId,func,knownTypes=0,isText,isHtml,isImage,txtFlag,processFileLoaded=function(){var msg=msgProgress.setTokens({'index':i+1,'files':numFiles,'percent':50,'name':caption});setTimeout(function(){$status.html(msg);self._updateFileDetails(numFiles);readFile(i+1);},self.processDelay);self._raise('fileloaded',[file,previewId,i,reader]);};if(!file){return;}fileId=self.fileManager.getId(file);if(typLen>0){for(j=0;j<typLen;j++){typ1=fileTypes[j];typ2=self.msgFileTypes[typ1]||typ1;strTypes+=j===0?typ2:', '+typ2;}}if(caption===false){readFile(i+1);return;}if(caption.length===0){msg=self.msgInvalidFileName.replace('{name}',$h.htmlEncode($h.getFileName(file),'[unknown]'));throwError(msg,file,previewId,i,fileId);return;}if(!$h.isEmpty(fileExt)){fileExtExpr=new RegExp('\\.('+fileExt.join('|')+')$','i');}fSizeKB=fileSize.toFixed(2);if(self.maxFileSize>0&&fileSize>self.maxFileSize){msg=self.msgSizeTooLarge.setTokens({'name':caption,'size':fSizeKB,'maxSize':self.maxFileSize});throwError(msg,file,previewId,i,fileId);return;}if(self.minFileSize!==null&&fileSize<=$h.getNum(self.minFileSize)){msg=self.msgSizeTooSmall.setTokens({'name':caption,'size':fSizeKB,'minSize':self.minFileSize});throwError(msg,file,previewId,i,fileId);return;}if(!$h.isEmpty(fileTypes)&&$h.isArray(fileTypes)){for(j=0;j<fileTypes.length;j+=1){typ=fileTypes[j];func=settings[typ];fileCount+=!func||(typeof func!=='function')?0:(func(file.type,$h.getFileName(file))?1:0);}if(fileCount===0){msg=self.msgInvalidFileType.setTokens({name:caption,types:strTypes});throwError(msg,file,previewId,i,fileId);return;}}if(fileCount===0&&!$h.isEmpty(fileExt)&&$h.isArray(fileExt)&&!$h.isEmpty(fileExtExpr)){chk=$h.compare(caption,fileExtExpr);fileCount+=$h.isEmpty(chk)?0:chk.length;if(fileCount===0){msg=self.msgInvalidFileExtension.setTokens({name:caption,extensions:strExt});throwError(msg,file,previewId,i,fileId);return;}}if(self.isAjaxUpload&&self.fileManager.exists(fileId)){msg=self.msgDuplicateFile.setTokens({name:caption,size:fSizeKB});throwError(msg,file,previewId,i,fileId);$thumb=$('#'+previewId);if($thumb&&$thumb.length){$thumb.remove();}return;}if(!self.canPreview(file)){if(self.isAjaxUpload){self.fileManager.add(file);}if(self.showPreview){$container.addClass('file-thumb-loading');self._previewDefault(file,previewId);self._initFileActions();}setTimeout(function(){self._updateFileDetails(numFiles);readFile(i+1);self._raise('fileloaded',[file,previewId,i]);},10);return;}isText=fnText(file.type,caption);isHtml=fnHtml(file.type,caption);isImage=fnImage(file.type,caption);$status.html(msgLoading.replace('{index}',i+1).replace('{files}',numFiles));$container.addClass('file-thumb-loading');reader.onerror=function(evt){self._errorHandler(evt,caption);};reader.onload=function(theFile){var hex,fileInfo,uint,byte,bytes=[],contents,mime,readTextImage=function(textFlag){var newReader=new FileReader();newReader.onerror=function(theFileNew){self._errorHandler(theFileNew,caption);};newReader.onload=function(theFileNew){self._previewFile(i,file,theFileNew,previewId,previewData,fileInfo);self._initFileActions();processFileLoaded();};if(textFlag){newReader.readAsText(file,self.textEncoding);}else{newReader.readAsDataURL(file);}};fileInfo={'name':caption,'type':file.type};$.each(settings,function(k,f){if(k!=='object'&&k!=='other'&&typeof f==='function'&&f(file.type,caption)){knownTypes++;}});if(knownTypes===0){uint=new Uint8Array(theFile.target.result);for(j=0;j<uint.length;j++){byte=uint[j].toString(16);bytes.push(byte);}hex=bytes.join('').toLowerCase().substring(0,8);mime=$h.getMimeType(hex,'','');if($h.isEmpty(mime)){contents=$h.arrayBuffer2String(reader.result);mime=$h.isSvg(contents)?'image/svg+xml':$h.getMimeType(hex,contents,file.type);}fileInfo={'name':caption,'type':mime};isText=fnText(mime,'');isHtml=fnHtml(mime,'');isImage=fnImage(mime,'');txtFlag=isText||isHtml;if(txtFlag||isImage){readTextImage(txtFlag);return;}}self._previewFile(i,file,theFile,previewId,previewData,fileInfo);self._initFileActions();processFileLoaded();};reader.onprogress=function(data){if(data.lengthComputable){var fact=(data.loaded/data.total)*100,progress=Math.ceil(fact);msg=msgProgress.setTokens({'index':i+1,'files':numFiles,'percent':progress,'name':caption});setTimeout(function(){$status.html(msg);},self.processDelay);}};if(isText||isHtml){reader.readAsText(file,self.textEncoding);}else{if(isImage){reader.readAsDataURL(file);}else{reader.readAsArrayBuffer(file);}}self.fileManager.add(file);};readFile(0);self._updateFileDetails(numFiles,false);},lock:function(){var self=this,$container=self.$container;self._resetErrors();self.disable();$container.addClass('is-locked');if(self.showCancel){$container.find('.fileinput-cancel').show();}if(self.showPause){$container.find('.fileinput-pause').show();}self._raise('filelock',[self.fileManager.stack,self._getExtraData()]);return self.$element;},unlock:function(reset){var self=this,$container=self.$container;if(reset===undefined){reset=true;}self.enable();$container.removeClass('is-locked');if(self.showCancel){$container.find('.fileinput-cancel').hide();}if(self.showPause){$container.find('.fileinput-pause').hide();}if(reset){self._resetFileStack();}self._raise('fileunlock',[self.fileManager.stack,self._getExtraData()]);return self.$element;},resume:function(){var self=this,flag=false,$pr=self.$progress,rm=self.resumableManager;if(!self.enableResumableUpload){return self.$element;}if(self.paused){$pr.html(self.progressPauseTemplate.setTokens({percent:101,status:self.msgUploadResume,stats:''}));}else{flag=true;}self.paused=false;if(flag){$pr.html(self.progressInfoTemplate.setTokens({percent:101,status:self.msgUploadBegin,stats:''}));}setTimeout(function(){rm.upload();},self.processDelay);return self.$element;},pause:function(){var self=this,rm=self.resumableManager,xhr=self.ajaxRequests,len=xhr.length,i,pct=rm.getProgress(),actions=self.fileActionSettings;if(!self.enableResumableUpload){return self.$element;}if(rm.chunkIntervalId){clearInterval(rm.chunkIntervalId);}if(self.ajaxQueueIntervalId){clearInterval(self.ajaxQueueIntervalId);}self._raise('fileuploadpaused',[self.fileManager,rm]);if(len>0){for(i=0;i<len;i+=1){self.paused=true;xhr[i].abort();}}if(self.showPreview){self._getThumbs().each(function(){var $thumb=$(this),fileId=$thumb.attr('data-fileid'),t=self._getLayoutTemplate('stats'),stats,$indicator=$thumb.find('.file-upload-indicator');$thumb.removeClass('file-uploading');if($indicator.attr('title')===actions.indicatorLoadingTitle){self._setThumbStatus($thumb,'Paused');stats=t.setTokens({pendingTime:self.msgPaused,uploadSpeed:''});self.paused=true;self._setProgress(pct,$thumb.find('.file-thumb-progress'),pct+'%',stats);}if(!self.fileManager.getFile(fileId)){$thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');}});}self._setProgress(101,self.$progress,self.msgPaused);return self.$element;},cancel:function(){var self=this,xhr=self.ajaxRequests,rm=self.resumableManager,len=xhr.length,i;if(self.enableResumableUpload&&rm.chunkIntervalId){clearInterval(rm.chunkIntervalId);rm.reset();self._raise('fileuploadcancelled',[self.fileManager,rm]);}else{self._raise('fileuploadcancelled',[self.fileManager]);}if(self.ajaxQueueIntervalId){clearInterval(self.ajaxQueueIntervalId);}self._initAjax();if(len>0){for(i=0;i<len;i+=1){self.cancelling=true;xhr[i].abort();}}self._getThumbs().each(function(){var $thumb=$(this),fileId=$thumb.attr('data-fileid'),$prog=$thumb.find('.file-thumb-progress');$thumb.removeClass('file-uploading');self._setProgress(0,$prog);$prog.hide();if(!self.fileManager.getFile(fileId)){$thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');$thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');}self.unlock();});setTimeout(function(){self._setProgressCancelled();},self.processDelay);return self.$element;},clear:function(){var self=this,cap;if(!self._raise('fileclear')){return;}self.$btnUpload.removeAttr('disabled');self._getThumbs().find('video,audio,img').each(function(){$h.cleanMemory($(this));});self._clearFileInput();self._resetUpload();self.clearFileStack();self._resetErrors(true);if(self._hasInitialPreview()){self._showFileIcon();self._resetPreview();self._initPreviewActions();self.$container.removeClass('file-input-new');}else{self._getThumbs().each(function(){self._clearObjects($(this));});if(self.isAjaxUpload){self.previewCache.data={};}self.$preview.html('');cap=(!self.overwriteInitial&&self.initialCaption.length>0)?self.initialCaption:'';self.$caption.attr('title','').val(cap);$h.addCss(self.$container,'file-input-new');self._validateDefaultPreview();}if(self.$container.find($h.FRAMES).length===0){if(!self._initCaption()){self.$captionContainer.removeClass('icon-visible');}}self._hideFileIcon();self.$captionContainer.focus();self._setFileDropZoneTitle();self._raise('filecleared');return self.$element;},reset:function(){var self=this;if(!self._raise('filereset')){return;}self.lastProgress=0;self._resetPreview();self.$container.find('.fileinput-filename').text('');$h.addCss(self.$container,'file-input-new');if(self.getFrames().length||self.dropZoneEnabled){self.$container.removeClass('file-input-new');}self.clearFileStack();self._setFileDropZoneTitle();return self.$element;},disable:function(){var self=this;self.isDisabled=true;self._raise('filedisabled');self.$element.attr('disabled','disabled');self.$container.find('.kv-fileinput-caption').addClass('file-caption-disabled');self.$container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button').attr('disabled',true);$h.addCss(self.$container.find('.btn-file'),'disabled');self._initDragDrop();return self.$element;},enable:function(){var self=this;self.isDisabled=false;self._raise('fileenabled');self.$element.removeAttr('disabled');self.$container.find('.kv-fileinput-caption').removeClass('file-caption-disabled');self.$container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button').removeAttr('disabled');self.$container.find('.btn-file').removeClass('disabled');self._initDragDrop();return self.$element;},upload:function(){var self=this,fm=self.fileManager,totLen=fm.count(),i,outData,len,hasExtraData=!$.isEmptyObject(self._getExtraData());if(!self.isAjaxUpload||self.isDisabled||!self._isFileSelectionValid(totLen)){return;}self.lastProgress=0;self._resetUpload();if(totLen===0&&!hasExtraData){self._showFileError(self.msgUploadEmpty);return;}self.cancelling=false;self.$progress.show();self.lock();len=fm.count();if(totLen===0&&hasExtraData){self._setProgress(2);self._uploadExtraOnly();return;}if(self.enableResumableUpload){return self.resume();}if(self.uploadAsync||self.enableResumableUpload){outData=self._getOutData(null);self._raise('filebatchpreupload',[outData]);self.fileBatchCompleted=false;self.uploadCache={content:[],config:[],tags:[],append:true};for(i=0;i<len;i++){self.uploadCache.content[i]=null;self.uploadCache.config[i]=null;self.uploadCache.tags[i]=null;}self.$preview.find('.file-preview-initial').removeClass($h.SORT_CSS);self._initSortable();self.cacheInitialPreview=self.getPreview();}self._setProgress(2);self.hasInitData=false;if(self.uploadAsync){i=0;$.each(fm.stack,function(id){self._uploadSingle(i,id,true);i++;});return;}self._uploadBatch();return self.$element;},destroy:function(){var self=this,$form=self.$form,$cont=self.$container,$el=self.$element,ns=self.namespace;$(document).off(ns);$(window).off(ns);if($form&&$form.length){$form.off(ns);}if(self.isAjaxUpload){self._clearFileInput();}self._cleanup();self._initPreviewCache();$el.insertBefore($cont).off(ns).removeData();$cont.off().remove();return $el;},refresh:function(options){var self=this,$el=self.$element;if(typeof options!=='object'||$h.isEmpty(options)){options=self.options;}else{options=$.extend(true,{},self.options,options);}self._init(options,true);self._listen();return $el;},zoom:function(frameId){var self=this,$frame=self._getFrame(frameId),$modal=self.$modal;if(!$frame){return;}$h.initModal($modal);$modal.html(self._getModalContent());self._setZoomContent($frame);$modal.modal('show');self._initZoomButtons();},getExif:function(frameId){var self=this,$frame=self._getFrame(frameId);return $frame&&$frame.data('exif')||null;},getFrames:function(cssFilter){var self=this,$frames;cssFilter=cssFilter||'';$frames=self.$preview.find($h.FRAMES+cssFilter);if(self.reversePreviewOrder){$frames=$($frames.get().reverse());}return $frames;},getPreview:function(){var self=this;return{content:self.initialPreview,config:self.initialPreviewConfig,tags:self.initialPreviewThumbTags};}};$.fn.fileinput=function(option){if(!$h.hasFileAPISupport()&&!$h.isIE(9)){return;}var args=Array.apply(null,arguments),retvals=[];args.shift();this.each(function(){var self=$(this),data=self.data('fileinput'),options=typeof option==='object'&&option,theme=options.theme||self.data('theme'),l={},t={},lang=options.language||self.data('language')||$.fn.fileinput.defaults.language||'en',opt;if(!data){if(theme){t=$.fn.fileinputThemes[theme]||{};}if(lang!=='en'&&!$h.isEmpty($.fn.fileinputLocales[lang])){l=$.fn.fileinputLocales[lang]||{};}opt=$.extend(true,{},$.fn.fileinput.defaults,t,$.fn.fileinputLocales.zh,l,options,self.data());data=new FileInput(this,opt);self.data('fileinput',data);}if(typeof option==='string'){retvals.push(data[option].apply(data,args));}});switch(retvals.length){case 0:return this;case 1:return retvals[0];default:return retvals;}};$.fn.fileinput.defaults={language:'en',showCaption:true,showBrowse:true,showPreview:true,showRemove:true,showUpload:true,showUploadStats:true,showCancel:null,showPause:null,showClose:true,showUploadedThumbs:true,browseOnZoneClick:false,autoReplace:false,autoOrientImage:function(){var ua=window.navigator.userAgent,webkit=!!ua.match(/WebKit/i),iOS=!!ua.match(/iP(od|ad|hone)/i),iOSSafari=iOS&&webkit&&!ua.match(/CriOS/i);return!iOSSafari;},autoOrientImageInitial:true,required:false,rtl:false,hideThumbnailContent:false,encodeUrl:true,generateFileId:null,previewClass:'',captionClass:'',frameClass:'krajee-default',mainClass:'file-caption-main',mainTemplate:null,purifyHtml:true,fileSizeGetter:null,initialCaption:'',initialPreview:[],initialPreviewDelimiter:'*$$*',initialPreviewAsData:false,initialPreviewFileType:'image',initialPreviewConfig:[],initialPreviewThumbTags:[],previewThumbTags:{},initialPreviewShowDelete:true,initialPreviewDownloadUrl:'',removeFromPreviewOnError:false,deleteUrl:'',deleteExtraData:{},overwriteInitial:true,sanitizeZoomCache:function(content){var $container=$(document.createElement('div')).append(content);$container.find('input,select,.file-thumbnail-footer').remove();return $container.html();},previewZoomButtonIcons:{prev:'<i class="glyphicon glyphicon-triangle-left"></i>',next:'<i class="glyphicon glyphicon-triangle-right"></i>',toggleheader:'<i class="glyphicon glyphicon-resize-vertical"></i>',fullscreen:'<i class="glyphicon glyphicon-fullscreen"></i>',borderless:'<i class="glyphicon glyphicon-resize-full"></i>',close:'<i class="glyphicon glyphicon-remove"></i>'},previewZoomButtonClasses:{prev:'btn btn-navigate',next:'btn btn-navigate',toggleheader:'btn btn-sm btn-kv btn-default btn-outline-secondary',fullscreen:'btn btn-sm btn-kv btn-default btn-outline-secondary',borderless:'btn btn-sm btn-kv btn-default btn-outline-secondary',close:'btn btn-sm btn-kv btn-default btn-outline-secondary'},previewTemplates:{},previewContentTemplates:{},preferIconicPreview:false,preferIconicZoomPreview:false,allowedFileTypes:null,allowedFileExtensions:null,allowedPreviewTypes:undefined,allowedPreviewMimeTypes:null,allowedPreviewExtensions:null,disabledPreviewTypes:undefined,disabledPreviewExtensions:['msi','exe','com','zip','rar','app','vb','scr'],disabledPreviewMimeTypes:null,defaultPreviewContent:null,customLayoutTags:{},customPreviewTags:{},previewFileIcon:'<i class="glyphicon glyphicon-file"></i>',previewFileIconClass:'file-other-icon',previewFileIconSettings:{},previewFileExtSettings:{},buttonLabelClass:'hidden-xs',browseIcon:'<i class="glyphicon glyphicon-folder-open"></i>&nbsp;',browseClass:'btn btn-primary',removeIcon:'<i class="glyphicon glyphicon-trash"></i>',removeClass:'btn btn-default btn-secondary',cancelIcon:'<i class="glyphicon glyphicon-ban-circle"></i>',cancelClass:'btn btn-default btn-secondary',pauseIcon:'<i class="glyphicon glyphicon-pause"></i>',pauseClass:'btn btn-default btn-secondary',uploadIcon:'<i class="glyphicon glyphicon-upload"></i>',uploadClass:'btn btn-default btn-secondary',uploadUrl:null,uploadUrlThumb:null,uploadAsync:true,uploadParamNames:{chunkCount:'chunkCount',chunkIndex:'chunkIndex',chunkSize:'chunkSize',chunkSizeStart:'chunkSizeStart',chunksUploaded:'chunksUploaded',fileBlob:'fileBlob',fileId:'fileId',fileName:'fileName',fileRelativePath:'fileRelativePath',fileSize:'fileSize',retryCount:'retryCount'},maxAjaxThreads:5,processDelay:100,queueDelay:10,progressDelay:0,enableResumableUpload:false,resumableUploadOptions:{fallback:null,testUrl:null,chunkSize:2*1024,maxThreads:4,maxRetries:3,showErrorLog:true},uploadExtraData:{},zoomModalHeight:480,minImageWidth:null,minImageHeight:null,maxImageWidth:null,maxImageHeight:null,resizeImage:false,resizePreference:'width',resizeQuality:0.92,resizeDefaultImageType:'image/jpeg',resizeIfSizeMoreThan:0,minFileSize:0,maxFileSize:0,maxFilePreviewSize:25600,minFileCount:0,maxFileCount:0,validateInitialCount:false,msgValidationErrorClass:'text-danger',msgValidationErrorIcon:'<i class="glyphicon glyphicon-exclamation-sign"></i> ',msgErrorClass:'file-error-message',progressThumbClass:'progress-bar progress-bar-striped active',progressClass:'progress-bar bg-success progress-bar-success progress-bar-striped active',progressInfoClass:'progress-bar bg-info progress-bar-info progress-bar-striped active',progressCompleteClass:'progress-bar bg-success progress-bar-success',progressPauseClass:'progress-bar bg-primary progress-bar-primary progress-bar-striped active',progressErrorClass:'progress-bar bg-danger progress-bar-danger',progressUploadThreshold:99,previewFileType:'image',elCaptionContainer:null,elCaptionText:null,elPreviewContainer:null,elPreviewImage:null,elPreviewStatus:null,elErrorContainer:null,errorCloseButton:$h.closeButton('kv-error-close'),slugCallback:null,dropZoneEnabled:true,dropZoneTitleClass:'file-drop-zone-title',fileActionSettings:{},otherActionButtons:'',textEncoding:'UTF-8',ajaxSettings:{},ajaxDeleteSettings:{},showAjaxErrorDetails:true,mergeAjaxCallbacks:false,mergeAjaxDeleteCallbacks:false,retryErrorUploads:true,reversePreviewOrder:false,usePdfRenderer:function(){var isIE11=!!window.MSInputMethodContext&&!!document.documentMode;return!!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i)||isIE11;},pdfRendererUrl:'',pdfRendererTemplate:'<iframe class="kv-preview-data file-preview-pdf" src="{renderer}?file={data}" {style}></iframe>'};$.fn.fileinputLocales.en={fileSingle:'file',filePlural:'files',browseLabel:'Browse &hellip;',removeLabel:'Remove',removeTitle:'Clear all unprocessed files',cancelLabel:'Cancel',cancelTitle:'Abort ongoing upload',pauseLabel:'Pause',pauseTitle:'Pause ongoing upload',uploadLabel:'Upload',uploadTitle:'Upload selected files',msgNo:'No',msgNoFilesSelected:'No files selected',msgCancelled:'Cancelled',msgPaused:'Paused',msgPlaceholder:'Select {files}...',msgZoomModalHeading:'Detailed Preview',msgFileRequired:'You must select a file to upload.',msgSizeTooSmall:'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',msgSizeTooLarge:'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',msgFilesTooLess:'You must select at least <b>{n}</b> {files} to upload.',msgFilesTooMany:'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',msgFileNotFound:'File "{name}" not found!',msgFileSecured:'Security restrictions prevent reading the file "{name}".',msgFileNotReadable:'File "{name}" is not readable.',msgFilePreviewAborted:'File preview aborted for "{name}".',msgFilePreviewError:'An error occurred while reading the file "{name}".',msgInvalidFileName:'Invalid or unsupported characters in file name "{name}".',msgInvalidFileType:'Invalid type for file "{name}". Only "{types}" files are supported.',msgInvalidFileExtension:'Invalid extension for file "{name}". Only "{extensions}" files are supported.',msgFileTypes:{'image':'image','html':'HTML','text':'text','video':'video','audio':'audio','flash':'flash','pdf':'PDF','object':'object'},msgUploadAborted:'The file upload was aborted',msgUploadThreshold:'Processing...',msgUploadBegin:'Initializing...',msgUploadEnd:'Done',msgUploadResume:'Resuming upload...',msgUploadEmpty:'No valid data available for upload.',msgUploadError:'Upload Error',msgDeleteError:'Delete Error',msgProgressError:'Error',msgValidationError:'Validation Error',msgLoading:'Loading file {index} of {files} &hellip;',msgProgress:'Loading file {index} of {files} - {name} - {percent}% completed.',msgSelected:'{n} {files} selected',msgFoldersNotAllowed:'Drag & drop files only! {n} folder(s) dropped were skipped.',msgImageWidthSmall:'Width of image file "{name}" must be at least {size} px.',msgImageHeightSmall:'Height of image file "{name}" must be at least {size} px.',msgImageWidthLarge:'Width of image file "{name}" cannot exceed {size} px.',msgImageHeightLarge:'Height of image file "{name}" cannot exceed {size} px.',msgImageResizeError:'Could not get the image dimensions to resize.',msgImageResizeException:'Error while resizing the image.<pre>{errors}</pre>',msgAjaxError:'Something went wrong with the {operation} operation. Please try again later!',msgAjaxProgressError:'{operation} failed',msgDuplicateFile:'File "{name}" of same size "{size} KB" has already been selected earlier. Skipping duplicate selection.',msgResumableUploadRetriesExceeded:'Upload aborted beyond <b>{max}</b> retries for file <b>{file}</b>! Error Details: <pre>{error}</pre>',msgPendingTime:'{time} remaining',msgCalculatingTime:'calculating time remaining',ajaxOperations:{deleteThumb:'file delete',uploadThumb:'file upload',uploadBatch:'batch file upload',uploadExtra:'form data upload'},dropZoneTitle:'Drag & drop files here &hellip;',dropZoneClickTitle:'<br>(or click to select {files})',previewZoomButtonTitles:{prev:'View previous file',next:'View next file',toggleheader:'Toggle header',fullscreen:'Toggle full screen',borderless:'Toggle borderless mode',close:'Close detailed preview'}};$.fn.fileinputLocales.zh={fileSingle:'文件',filePlural:'个文件',browseLabel:'选择 &hellip;',removeLabel:'移除',removeTitle:'清除选中文件',cancelLabel:'取消',cancelTitle:'取消进行中的上传',pauseLabel:'Pause',pauseTitle:'Pause ongoing upload',uploadLabel:'上传',uploadTitle:'上传选中文件',msgNo:'没有',msgNoFilesSelected:'未选择文件',msgPaused:'Paused',msgCancelled:'取消',msgPlaceholder:'选择 {files}...',msgZoomModalHeading:'详细预览',msgFileRequired:'必须选择一个文件上传.',msgSizeTooSmall:'文件 "{name}" (<b>{size} KB</b>) 必须大于限定大小 <b>{minSize} KB</b>.',msgSizeTooLarge:'文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.',msgFilesTooLess:'你必须选择最少 <b>{n}</b> {files} 来上传. ',msgFilesTooMany:'选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.',msgFileNotFound:'文件 "{name}" 未找到!',msgFileSecured:'安全限制,为了防止读取文件 "{name}".',msgFileNotReadable:'文件 "{name}" 不可读.',msgFilePreviewAborted:'取消 "{name}" 的预览.',msgFilePreviewError:'读取 "{name}" 时出现了一个错误.',msgInvalidFileName:'文件名 "{name}" 包含非法字符.',msgInvalidFileType:'不正确的类型 "{name}". 只支持 "{types}" 类型的文件.',msgInvalidFileExtension:'不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.',msgFileTypes:{'image':'image','html':'HTML','text':'text','video':'video','audio':'audio','flash':'flash','pdf':'PDF','object':'object'},msgUploadAborted:'该文件上传被中止',msgUploadThreshold:'处理中...',msgUploadBegin:'正在初始化...',msgUploadEnd:'完成',msgUploadResume:'Resuming upload...',msgUploadEmpty:'无效的文件上传.',msgUploadError:'Upload Error',msgDeleteError:'Delete Error',msgProgressError:'上传出错',msgValidationError:'验证错误',msgLoading:'加载第 {index} 文件 共 {files} &hellip;',msgProgress:'加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.',msgSelected:'{n} {files} 选中',msgFoldersNotAllowed:'只支持拖拽文件! 跳过 {n} 拖拽的文件夹.',msgImageWidthSmall:'图像文件的"{name}"的宽度必须是至少{size}像素.',msgImageHeightSmall:'图像文件的"{name}"的高度必须至少为{size}像素.',msgImageWidthLarge:'图像文件"{name}"的宽度不能超过{size}像素.',msgImageHeightLarge:'图像文件"{name}"的高度不能超过{size}像素.',msgImageResizeError:'无法获取的图像尺寸调整。',msgImageResizeException:'调整图像大小时发生错误。<pre>{errors}</pre>',msgAjaxError:'{operation} 发生错误. 请重试!',msgAjaxProgressError:'{operation} 失败',msgDuplicateFile:'File "{name}" of same size "{size} KB" has already been selected earlier. Skipping duplicate selection.',msgResumableUploadRetriesExceeded:'Upload aborted beyond <b>{max}</b> retries for file <b>{file}</b>! Error Details: <pre>{error}</pre>',msgPendingTime:'{time} remaining',msgCalculatingTime:'calculating time remaining',ajaxOperations:{deleteThumb:'删除文件',uploadThumb:'上传文件',uploadBatch:'批量上传',uploadExtra:'表单数据上传'},dropZoneTitle:'拖拽文件到这里 &hellip;<br>支持多文件同时上传',dropZoneClickTitle:'<br>(或点击{files}按钮选择文件)',fileActionSettings:{removeTitle:'删除文件',uploadTitle:'上传文件',downloadTitle:'下载文件',uploadRetryTitle:'重试',zoomTitle:'查看详情',dragTitle:'移动 / 重置',indicatorNewTitle:'没有上传',indicatorSuccessTitle:'上传',indicatorErrorTitle:'上传错误',indicatorPausedTitle:'Upload Paused',indicatorLoadingTitle:'上传 ...'},previewZoomButtonTitles:{prev:'预览上一个文件',next:'预览下一个文件',toggleheader:'缩放',fullscreen:'全屏',borderless:'无边界模式',close:'关闭当前预览'}};$.fn.fileinput.Constructor=FileInput;$(document).ready(function(){var $input=$('input.file[type=file]');if($input.length){$input.fileinput();}});}));
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif b/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif
new file mode 100644
index 0000000..44e3b7a
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif
diff --git a/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading.gif b/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading.gif
new file mode 100644
index 0000000..0ea146c
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading.gif
diff --git a/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css
new file mode 100644
index 0000000..b07486c
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css
@@ -0,0 +1,429 @@
+/*!
+ * Bootstrap-select v1.13.10 (https://developer.snapappointments.com/bootstrap-select)
+ *
+ * Copyright 2012-2019 SnapAppointments, LLC
+ * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
+ */
+
+select.bs-select-hidden,
+.bootstrap-select > select.bs-select-hidden,
+select.selectpicker {
+  display: none !important;
+}
+.bootstrap-select {
+  width: 220px \0;
+  /*IE9 and below*/
+  vertical-align: middle;
+}
+.bootstrap-select > .dropdown-toggle {
+  position: relative;
+  width: 100%;
+  text-align: right;
+  white-space: nowrap;
+  display: -webkit-inline-box;
+  display: -webkit-inline-flex;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  -webkit-box-align: center;
+  -webkit-align-items: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-box-pack: justify;
+  -webkit-justify-content: space-between;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+}
+.bootstrap-select > .dropdown-toggle:after {
+  margin-top: -1px;
+}
+.bootstrap-select > .dropdown-toggle.bs-placeholder,
+.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder:active {
+  color: #999;
+}
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:active {
+  color: rgba(255, 255, 255, 0.5);
+}
+.bootstrap-select > select {
+  position: absolute !important;
+  bottom: 0;
+  left: 50%;
+  display: block !important;
+  width: 0.5px !important;
+  height: 100% !important;
+  padding: 0 !important;
+  opacity: 0 !important;
+  border: none;
+  z-index: 0 !important;
+}
+.bootstrap-select > select.mobile-device {
+  top: 0;
+  left: 0;
+  display: block !important;
+  width: 100% !important;
+  z-index: 2 !important;
+}
+.has-error .bootstrap-select .dropdown-toggle,
+.error .bootstrap-select .dropdown-toggle,
+.bootstrap-select.is-invalid .dropdown-toggle,
+.was-validated .bootstrap-select .selectpicker:invalid + .dropdown-toggle {
+  border-color: #b94a48;
+}
+.bootstrap-select.is-valid .dropdown-toggle,
+.was-validated .bootstrap-select .selectpicker:valid + .dropdown-toggle {
+  border-color: #28a745;
+}
+.bootstrap-select.fit-width {
+  width: auto !important;
+}
+.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
+  width: 220px;
+}
+.bootstrap-select > select.mobile-device:focus + .dropdown-toggle,
+.bootstrap-select .dropdown-toggle:focus {
+  outline: thin dotted #333333 !important;
+  outline: 5px auto -webkit-focus-ring-color !important;
+  outline-offset: -2px;
+}
+.bootstrap-select.form-control {
+  margin-bottom: 0;
+  padding: 0;
+  border: none;
+  height: auto;
+}
+:not(.input-group) > .bootstrap-select.form-control:not([class*="col-"]) {
+  width: 100%;
+}
+.bootstrap-select.form-control.input-group-btn {
+  float: none;
+  z-index: auto;
+}
+.form-inline .bootstrap-select,
+.form-inline .bootstrap-select.form-control:not([class*="col-"]) {
+  width: auto;
+}
+.bootstrap-select:not(.input-group-btn),
+.bootstrap-select[class*="col-"] {
+  float: none;
+  display: inline-block;
+  margin-left: 0;
+}
+.bootstrap-select.dropdown-menu-right,
+.bootstrap-select[class*="col-"].dropdown-menu-right,
+.row .bootstrap-select[class*="col-"].dropdown-menu-right {
+  float: right;
+}
+.form-inline .bootstrap-select,
+.form-horizontal .bootstrap-select,
+.form-group .bootstrap-select {
+  margin-bottom: 0;
+}
+.form-group-lg .bootstrap-select.form-control,
+.form-group-sm .bootstrap-select.form-control {
+  padding: 0;
+}
+.form-group-lg .bootstrap-select.form-control .dropdown-toggle,
+.form-group-sm .bootstrap-select.form-control .dropdown-toggle {
+  height: 100%;
+  font-size: inherit;
+  line-height: inherit;
+  border-radius: inherit;
+}
+.bootstrap-select.form-control-sm .dropdown-toggle,
+.bootstrap-select.form-control-lg .dropdown-toggle {
+  font-size: inherit;
+  line-height: inherit;
+  border-radius: inherit;
+}
+.bootstrap-select.form-control-sm .dropdown-toggle {
+  padding: 0.25rem 0.5rem;
+}
+.bootstrap-select.form-control-lg .dropdown-toggle {
+  padding: 0.5rem 1rem;
+}
+.form-inline .bootstrap-select .form-control {
+  width: 100%;
+}
+.bootstrap-select.disabled,
+.bootstrap-select > .disabled {
+  cursor: not-allowed;
+}
+.bootstrap-select.disabled:focus,
+.bootstrap-select > .disabled:focus {
+  outline: none !important;
+}
+.bootstrap-select.bs-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 0 !important;
+  padding: 0 !important;
+}
+.bootstrap-select.bs-container .dropdown-menu {
+  z-index: 1060;
+}
+.bootstrap-select .dropdown-toggle .filter-option {
+  position: static;
+  top: 0;
+  left: 0;
+  float: left;
+  height: 100%;
+  width: 100%;
+  text-align: left;
+  overflow: hidden;
+  -webkit-box-flex: 0;
+  -webkit-flex: 0 1 auto;
+      -ms-flex: 0 1 auto;
+          flex: 0 1 auto;
+}
+.bs3.bootstrap-select .dropdown-toggle .filter-option {
+  padding-right: inherit;
+}
+.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option {
+  position: absolute;
+  padding-top: inherit;
+  padding-bottom: inherit;
+  padding-left: inherit;
+  float: none;
+}
+.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner {
+  padding-right: inherit;
+}
+.bootstrap-select .dropdown-toggle .filter-option-inner-inner {
+  overflow: hidden;
+}
+.bootstrap-select .dropdown-toggle .filter-expand {
+  width: 0 !important;
+  float: left;
+  opacity: 0 !important;
+  overflow: hidden;
+}
+.bootstrap-select .dropdown-toggle .caret {
+  position: absolute;
+  top: 50%;
+  right: 12px;
+  margin-top: -2px;
+  vertical-align: middle;
+}
+.input-group .bootstrap-select.form-control .dropdown-toggle {
+  border-radius: inherit;
+}
+.bootstrap-select[class*="col-"] .dropdown-toggle {
+  width: 100%;
+}
+.bootstrap-select .dropdown-menu {
+  min-width: 100%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bootstrap-select .dropdown-menu > .inner:focus {
+  outline: none !important;
+}
+.bootstrap-select .dropdown-menu.inner {
+  position: static;
+  float: none;
+  border: 0;
+  padding: 0;
+  margin: 0;
+  border-radius: 0;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.bootstrap-select .dropdown-menu li {
+  position: relative;
+}
+.bootstrap-select .dropdown-menu li.active small {
+  color: rgba(255, 255, 255, 0.5) !important;
+}
+.bootstrap-select .dropdown-menu li.disabled a {
+  cursor: not-allowed;
+}
+.bootstrap-select .dropdown-menu li a {
+  cursor: pointer;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+}
+.bootstrap-select .dropdown-menu li a.opt {
+  position: relative;
+  padding-left: 2.25em;
+}
+.bootstrap-select .dropdown-menu li a span.check-mark {
+  display: none;
+}
+.bootstrap-select .dropdown-menu li a span.text {
+  display: inline-block;
+}
+.bootstrap-select .dropdown-menu li small {
+  padding-left: 0.5em;
+}
+.bootstrap-select .dropdown-menu .notify {
+  position: absolute;
+  bottom: 5px;
+  width: 96%;
+  margin: 0 2%;
+  min-height: 26px;
+  padding: 3px 5px;
+  background: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  pointer-events: none;
+  opacity: 0.9;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bootstrap-select .no-results {
+  padding: 3px;
+  background: #f5f5f5;
+  margin: 0 5px;
+  white-space: nowrap;
+}
+.bootstrap-select.fit-width .dropdown-toggle .filter-option {
+  position: static;
+  display: inline;
+  padding: 0;
+}
+.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,
+.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner {
+  display: inline;
+}
+.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before {
+  content: '\00a0';
+}
+.bootstrap-select.fit-width .dropdown-toggle .caret {
+  position: static;
+  top: auto;
+  margin-top: -1px;
+}
+.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark {
+  position: absolute;
+  display: inline-block;
+  right: 15px;
+  top: 5px;
+}
+.bootstrap-select.show-tick .dropdown-menu li a span.text {
+  margin-right: 34px;
+}
+.bootstrap-select .bs-ok-default:after {
+  content: '';
+  display: block;
+  width: 0.5em;
+  height: 1em;
+  border-style: solid;
+  border-width: 0 0.26em 0.26em 0;
+  -webkit-transform: rotate(45deg);
+      -ms-transform: rotate(45deg);
+       -o-transform: rotate(45deg);
+          transform: rotate(45deg);
+}
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle,
+.bootstrap-select.show-menu-arrow.show > .dropdown-toggle {
+  z-index: 1061;
+}
+.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before {
+  content: '';
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid rgba(204, 204, 204, 0.2);
+  position: absolute;
+  bottom: -4px;
+  left: 9px;
+  display: none;
+}
+.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after {
+  content: '';
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid white;
+  position: absolute;
+  bottom: -4px;
+  left: 10px;
+  display: none;
+}
+.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before {
+  bottom: auto;
+  top: -4px;
+  border-top: 7px solid rgba(204, 204, 204, 0.2);
+  border-bottom: 0;
+}
+.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after {
+  bottom: auto;
+  top: -4px;
+  border-top: 6px solid white;
+  border-bottom: 0;
+}
+.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before {
+  right: 12px;
+  left: auto;
+}
+.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after {
+  right: 13px;
+  left: auto;
+}
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:before,
+.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:before,
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:after,
+.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:after {
+  display: block;
+}
+.bs-searchbox,
+.bs-actionsbox,
+.bs-donebutton {
+  padding: 4px 8px;
+}
+.bs-actionsbox {
+  width: 100%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bs-actionsbox .btn-group button {
+  width: 50%;
+}
+.bs-donebutton {
+  float: left;
+  width: 100%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bs-donebutton .btn-group button {
+  width: 100%;
+}
+.bs-searchbox + .bs-actionsbox {
+  padding: 0 8px 4px;
+}
+.bs-searchbox .form-control {
+  margin-bottom: 0;
+  width: 100%;
+  float: none;
+}
+/*# sourceMappingURL=bootstrap-select.css.map */
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js
new file mode 100644
index 0000000..2a4b26e
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js
@@ -0,0 +1,3139 @@
+/*!
+ * Bootstrap-select v1.13.10 (https://developer.snapappointments.com/bootstrap-select)
+ *
+ * Copyright 2012-2019 SnapAppointments, LLC
+ * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
+ */
+
+(function (root, factory) {
+  if (root === undefined && window !== undefined) root = window;
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define(["jquery"], function (a0) {
+      return (factory(a0));
+    });
+  } else if (typeof module === 'object' && module.exports) {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"));
+  } else {
+    factory(root["jQuery"]);
+  }
+}(this, function (jQuery) {
+
+(function ($) {
+  'use strict';
+
+  var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn'];
+
+  var uriAttrs = [
+    'background',
+    'cite',
+    'href',
+    'itemtype',
+    'longdesc',
+    'poster',
+    'src',
+    'xlink:href'
+  ];
+
+  var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
+
+  var DefaultWhitelist = {
+    // Global attributes allowed on any supplied element below.
+    '*': ['class', 'dir', 'id', 'lang', 'role', 'tabindex', 'style', ARIA_ATTRIBUTE_PATTERN],
+    a: ['target', 'href', 'title', 'rel'],
+    area: [],
+    b: [],
+    br: [],
+    col: [],
+    code: [],
+    div: [],
+    em: [],
+    hr: [],
+    h1: [],
+    h2: [],
+    h3: [],
+    h4: [],
+    h5: [],
+    h6: [],
+    i: [],
+    img: ['src', 'alt', 'title', 'width', 'height'],
+    li: [],
+    ol: [],
+    p: [],
+    pre: [],
+    s: [],
+    small: [],
+    span: [],
+    sub: [],
+    sup: [],
+    strong: [],
+    u: [],
+    ul: []
+  }
+
+  /**
+   * A pattern that recognizes a commonly useful subset of URLs that are safe.
+   *
+   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
+   */
+  var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi;
+
+  /**
+   * A pattern that matches safe data URLs. Only matches image, video and audio types.
+   *
+   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
+   */
+  var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;
+
+  function allowedAttribute (attr, allowedAttributeList) {
+    var attrName = attr.nodeName.toLowerCase()
+
+    if ($.inArray(attrName, allowedAttributeList) !== -1) {
+      if ($.inArray(attrName, uriAttrs) !== -1) {
+        return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))
+      }
+
+      return true
+    }
+
+    var regExp = $(allowedAttributeList).filter(function (index, value) {
+      return value instanceof RegExp
+    })
+
+    // Check if a regular expression validates the attribute.
+    for (var i = 0, l = regExp.length; i < l; i++) {
+      if (attrName.match(regExp[i])) {
+        return true
+      }
+    }
+
+    return false
+  }
+
+  function sanitizeHtml (unsafeElements, whiteList, sanitizeFn) {
+    if (sanitizeFn && typeof sanitizeFn === 'function') {
+      return sanitizeFn(unsafeElements);
+    }
+
+    var whitelistKeys = Object.keys(whiteList);
+
+    for (var i = 0, len = unsafeElements.length; i < len; i++) {
+      var elements = unsafeElements[i].querySelectorAll('*');
+
+      for (var j = 0, len2 = elements.length; j < len2; j++) {
+        var el = elements[j];
+        var elName = el.nodeName.toLowerCase();
+
+        if (whitelistKeys.indexOf(elName) === -1) {
+          el.parentNode.removeChild(el);
+
+          continue;
+        }
+
+        var attributeList = [].slice.call(el.attributes);
+        var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []);
+
+        for (var k = 0, len3 = attributeList.length; k < len3; k++) {
+          var attr = attributeList[k];
+
+          if (!allowedAttribute(attr, whitelistedAttributes)) {
+            el.removeAttribute(attr.nodeName);
+          }
+        }
+      }
+    }
+  }
+
+  // Polyfill for browsers with no classList support
+  // Remove in v2
+  if (!('classList' in document.createElement('_'))) {
+    (function (view) {
+      if (!('Element' in view)) return;
+
+      var classListProp = 'classList',
+          protoProp = 'prototype',
+          elemCtrProto = view.Element[protoProp],
+          objCtr = Object,
+          classListGetter = function () {
+            var $elem = $(this);
+
+            return {
+              add: function (classes) {
+                classes = Array.prototype.slice.call(arguments).join(' ');
+                return $elem.addClass(classes);
+              },
+              remove: function (classes) {
+                classes = Array.prototype.slice.call(arguments).join(' ');
+                return $elem.removeClass(classes);
+              },
+              toggle: function (classes, force) {
+                return $elem.toggleClass(classes, force);
+              },
+              contains: function (classes) {
+                return $elem.hasClass(classes);
+              }
+            }
+          };
+
+      if (objCtr.defineProperty) {
+        var classListPropDesc = {
+          get: classListGetter,
+          enumerable: true,
+          configurable: true
+        };
+        try {
+          objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+        } catch (ex) { // IE 8 doesn't support enumerable:true
+          // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
+          // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
+          if (ex.number === undefined || ex.number === -0x7FF5EC54) {
+            classListPropDesc.enumerable = false;
+            objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+          }
+        }
+      } else if (objCtr[protoProp].__defineGetter__) {
+        elemCtrProto.__defineGetter__(classListProp, classListGetter);
+      }
+    }(window));
+  }
+
+  var testElement = document.createElement('_');
+
+  testElement.classList.add('c1', 'c2');
+
+  if (!testElement.classList.contains('c2')) {
+    var _add = DOMTokenList.prototype.add,
+        _remove = DOMTokenList.prototype.remove;
+
+    DOMTokenList.prototype.add = function () {
+      Array.prototype.forEach.call(arguments, _add.bind(this));
+    }
+
+    DOMTokenList.prototype.remove = function () {
+      Array.prototype.forEach.call(arguments, _remove.bind(this));
+    }
+  }
+
+  testElement.classList.toggle('c3', false);
+
+  // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
+  // support the second argument.
+  if (testElement.classList.contains('c3')) {
+    var _toggle = DOMTokenList.prototype.toggle;
+
+    DOMTokenList.prototype.toggle = function (token, force) {
+      if (1 in arguments && !this.contains(token) === !force) {
+        return force;
+      } else {
+        return _toggle.call(this, token);
+      }
+    };
+  }
+
+  testElement = null;
+
+  // shallow array comparison
+  function isEqual (array1, array2) {
+    return array1.length === array2.length && array1.every(function (element, index) {
+      return element === array2[index];
+    });
+  };
+
+  // <editor-fold desc="Shims">
+  if (!String.prototype.startsWith) {
+    (function () {
+      'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
+      var defineProperty = (function () {
+        // IE 8 only supports `Object.defineProperty` on DOM elements
+        try {
+          var object = {};
+          var $defineProperty = Object.defineProperty;
+          var result = $defineProperty(object, object, object) && $defineProperty;
+        } catch (error) {
+        }
+        return result;
+      }());
+      var toString = {}.toString;
+      var startsWith = function (search) {
+        if (this == null) {
+          throw new TypeError();
+        }
+        var string = String(this);
+        if (search && toString.call(search) == '[object RegExp]') {
+          throw new TypeError();
+        }
+        var stringLength = string.length;
+        var searchString = String(search);
+        var searchLength = searchString.length;
+        var position = arguments.length > 1 ? arguments[1] : undefined;
+        // `ToInteger`
+        var pos = position ? Number(position) : 0;
+        if (pos != pos) { // better `isNaN`
+          pos = 0;
+        }
+        var start = Math.min(Math.max(pos, 0), stringLength);
+        // Avoid the `indexOf` call if no match is possible
+        if (searchLength + start > stringLength) {
+          return false;
+        }
+        var index = -1;
+        while (++index < searchLength) {
+          if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {
+            return false;
+          }
+        }
+        return true;
+      };
+      if (defineProperty) {
+        defineProperty(String.prototype, 'startsWith', {
+          'value': startsWith,
+          'configurable': true,
+          'writable': true
+        });
+      } else {
+        String.prototype.startsWith = startsWith;
+      }
+    }());
+  }
+
+  if (!Object.keys) {
+    Object.keys = function (
+      o, // object
+      k, // key
+      r  // result array
+    ) {
+      // initialize object and result
+      r = [];
+      // iterate over object keys
+      for (k in o) {
+        // fill result array with non-prototypical keys
+        r.hasOwnProperty.call(o, k) && r.push(k);
+      }
+      // return result
+      return r;
+    };
+  }
+
+  if (HTMLSelectElement && !HTMLSelectElement.prototype.hasOwnProperty('selectedOptions')) {
+    Object.defineProperty(HTMLSelectElement.prototype, 'selectedOptions', {
+      get: function () {
+        return this.querySelectorAll(':checked');
+      }
+    });
+  }
+
+  function getSelectedOptions (select, ignoreDisabled) {
+    var selectedOptions = select.selectedOptions,
+        options = [],
+        opt;
+
+    if (ignoreDisabled) {
+      for (var i = 0, len = selectedOptions.length; i < len; i++) {
+        opt = selectedOptions[i];
+
+        if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) {
+          options.push(opt);
+        }
+      }
+
+      return options;
+    }
+
+    return selectedOptions;
+  }
+
+  // much faster than $.val()
+  function getSelectValues (select, selectedOptions) {
+    var value = [],
+        options = selectedOptions || select.selectedOptions,
+        opt;
+
+    for (var i = 0, len = options.length; i < len; i++) {
+      opt = options[i];
+
+      if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) {
+        value.push(opt.value || opt.text);
+      }
+    }
+
+    if (!select.multiple) {
+      return !value.length ? null : value[0];
+    }
+
+    return value;
+  }
+
+  // set data-selected on select element if the value has been programmatically selected
+  // prior to initialization of bootstrap-select
+  // * consider removing or replacing an alternative method *
+  var valHooks = {
+    useDefault: false,
+    _set: $.valHooks.select.set
+  };
+
+  $.valHooks.select.set = function (elem, value) {
+    if (value && !valHooks.useDefault) $(elem).data('selected', true);
+
+    return valHooks._set.apply(this, arguments);
+  };
+
+  var changedArguments = null;
+
+  var EventIsSupported = (function () {
+    try {
+      new Event('change');
+      return true;
+    } catch (e) {
+      return false;
+    }
+  })();
+
+  $.fn.triggerNative = function (eventName) {
+    var el = this[0],
+        event;
+
+    if (el.dispatchEvent) { // for modern browsers & IE9+
+      if (EventIsSupported) {
+        // For modern browsers
+        event = new Event(eventName, {
+          bubbles: true
+        });
+      } else {
+        // For IE since it doesn't support Event constructor
+        event = document.createEvent('Event');
+        event.initEvent(eventName, true, false);
+      }
+
+      el.dispatchEvent(event);
+    } else if (el.fireEvent) { // for IE8
+      event = document.createEventObject();
+      event.eventType = eventName;
+      el.fireEvent('on' + eventName, event);
+    } else {
+      // fall back to jQuery.trigger
+      this.trigger(eventName);
+    }
+  };
+  // </editor-fold>
+
+  function stringSearch (li, searchString, method, normalize) {
+    var stringTypes = [
+          'display',
+          'subtext',
+          'tokens'
+        ],
+        searchSuccess = false;
+
+    for (var i = 0; i < stringTypes.length; i++) {
+      var stringType = stringTypes[i],
+          string = li[stringType];
+
+      if (string) {
+        string = string.toString();
+
+        // Strip HTML tags. This isn't perfect, but it's much faster than any other method
+        if (stringType === 'display') {
+          string = string.replace(/<[^>]+>/g, '');
+        }
+
+        if (normalize) string = normalizeToBase(string);
+        string = string.toUpperCase();
+
+        if (method === 'contains') {
+          searchSuccess = string.indexOf(searchString) >= 0;
+        } else {
+          searchSuccess = string.startsWith(searchString);
+        }
+
+        if (searchSuccess) break;
+      }
+    }
+
+    return searchSuccess;
+  }
+
+  function toInteger (value) {
+    return parseInt(value, 10) || 0;
+  }
+
+  // Borrowed from Lodash (_.deburr)
+  /** Used to map Latin Unicode letters to basic Latin letters. */
+  var deburredLetters = {
+    // Latin-1 Supplement block.
+    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+    '\xc7': 'C',  '\xe7': 'c',
+    '\xd0': 'D',  '\xf0': 'd',
+    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+    '\xcc': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+    '\xec': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
+    '\xd1': 'N',  '\xf1': 'n',
+    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
+    '\xc6': 'Ae', '\xe6': 'ae',
+    '\xde': 'Th', '\xfe': 'th',
+    '\xdf': 'ss',
+    // Latin Extended-A block.
+    '\u0100': 'A',  '\u0102': 'A', '\u0104': 'A',
+    '\u0101': 'a',  '\u0103': 'a', '\u0105': 'a',
+    '\u0106': 'C',  '\u0108': 'C', '\u010a': 'C', '\u010c': 'C',
+    '\u0107': 'c',  '\u0109': 'c', '\u010b': 'c', '\u010d': 'c',
+    '\u010e': 'D',  '\u0110': 'D', '\u010f': 'd', '\u0111': 'd',
+    '\u0112': 'E',  '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E',
+    '\u0113': 'e',  '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e',
+    '\u011c': 'G',  '\u011e': 'G', '\u0120': 'G', '\u0122': 'G',
+    '\u011d': 'g',  '\u011f': 'g', '\u0121': 'g', '\u0123': 'g',
+    '\u0124': 'H',  '\u0126': 'H', '\u0125': 'h', '\u0127': 'h',
+    '\u0128': 'I',  '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I',
+    '\u0129': 'i',  '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i',
+    '\u0134': 'J',  '\u0135': 'j',
+    '\u0136': 'K',  '\u0137': 'k', '\u0138': 'k',
+    '\u0139': 'L',  '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L',
+    '\u013a': 'l',  '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l',
+    '\u0143': 'N',  '\u0145': 'N', '\u0147': 'N', '\u014a': 'N',
+    '\u0144': 'n',  '\u0146': 'n', '\u0148': 'n', '\u014b': 'n',
+    '\u014c': 'O',  '\u014e': 'O', '\u0150': 'O',
+    '\u014d': 'o',  '\u014f': 'o', '\u0151': 'o',
+    '\u0154': 'R',  '\u0156': 'R', '\u0158': 'R',
+    '\u0155': 'r',  '\u0157': 'r', '\u0159': 'r',
+    '\u015a': 'S',  '\u015c': 'S', '\u015e': 'S', '\u0160': 'S',
+    '\u015b': 's',  '\u015d': 's', '\u015f': 's', '\u0161': 's',
+    '\u0162': 'T',  '\u0164': 'T', '\u0166': 'T',
+    '\u0163': 't',  '\u0165': 't', '\u0167': 't',
+    '\u0168': 'U',  '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U',
+    '\u0169': 'u',  '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u',
+    '\u0174': 'W',  '\u0175': 'w',
+    '\u0176': 'Y',  '\u0177': 'y', '\u0178': 'Y',
+    '\u0179': 'Z',  '\u017b': 'Z', '\u017d': 'Z',
+    '\u017a': 'z',  '\u017c': 'z', '\u017e': 'z',
+    '\u0132': 'IJ', '\u0133': 'ij',
+    '\u0152': 'Oe', '\u0153': 'oe',
+    '\u0149': "'n", '\u017f': 's'
+  };
+
+  /** Used to match Latin Unicode letters (excluding mathematical operators). */
+  var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
+
+  /** Used to compose unicode character classes. */
+  var rsComboMarksRange = '\\u0300-\\u036f',
+      reComboHalfMarksRange = '\\ufe20-\\ufe2f',
+      rsComboSymbolsRange = '\\u20d0-\\u20ff',
+      rsComboMarksExtendedRange = '\\u1ab0-\\u1aff',
+      rsComboMarksSupplementRange = '\\u1dc0-\\u1dff',
+      rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange;
+
+  /** Used to compose unicode capture groups. */
+  var rsCombo = '[' + rsComboRange + ']';
+
+  /**
+   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+   */
+  var reComboMark = RegExp(rsCombo, 'g');
+
+  function deburrLetter (key) {
+    return deburredLetters[key];
+  };
+
+  function normalizeToBase (string) {
+    string = string.toString();
+    return string && string.replace(reLatin, deburrLetter).replace(reComboMark, '');
+  }
+
+  // List of HTML entities for escaping.
+  var escapeMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '`': '&#x60;'
+  };
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  var createEscaper = function (map) {
+    var escaper = function (match) {
+      return map[match];
+    };
+    // Regexes for identifying a key that needs to be escaped.
+    var source = '(?:' + Object.keys(map).join('|') + ')';
+    var testRegexp = RegExp(source);
+    var replaceRegexp = RegExp(source, 'g');
+    return function (string) {
+      string = string == null ? '' : '' + string;
+      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+    };
+  };
+
+  var htmlEscape = createEscaper(escapeMap);
+
+  /**
+   * ------------------------------------------------------------------------
+   * Constants
+   * ------------------------------------------------------------------------
+   */
+
+  var keyCodeMap = {
+    32: ' ',
+    48: '0',
+    49: '1',
+    50: '2',
+    51: '3',
+    52: '4',
+    53: '5',
+    54: '6',
+    55: '7',
+    56: '8',
+    57: '9',
+    59: ';',
+    65: 'A',
+    66: 'B',
+    67: 'C',
+    68: 'D',
+    69: 'E',
+    70: 'F',
+    71: 'G',
+    72: 'H',
+    73: 'I',
+    74: 'J',
+    75: 'K',
+    76: 'L',
+    77: 'M',
+    78: 'N',
+    79: 'O',
+    80: 'P',
+    81: 'Q',
+    82: 'R',
+    83: 'S',
+    84: 'T',
+    85: 'U',
+    86: 'V',
+    87: 'W',
+    88: 'X',
+    89: 'Y',
+    90: 'Z',
+    96: '0',
+    97: '1',
+    98: '2',
+    99: '3',
+    100: '4',
+    101: '5',
+    102: '6',
+    103: '7',
+    104: '8',
+    105: '9'
+  };
+
+  var keyCodes = {
+    ESCAPE: 27, // KeyboardEvent.which value for Escape (Esc) key
+    ENTER: 13, // KeyboardEvent.which value for Enter key
+    SPACE: 32, // KeyboardEvent.which value for space key
+    TAB: 9, // KeyboardEvent.which value for tab key
+    ARROW_UP: 38, // KeyboardEvent.which value for up arrow key
+    ARROW_DOWN: 40 // KeyboardEvent.which value for down arrow key
+  }
+
+  var version = {
+    success: false,
+    major: '3'
+  };
+
+  try {
+    version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.');
+    version.major = version.full[0];
+    version.success = true;
+  } catch (err) {
+    // do nothing
+  }
+
+  var selectId = 0;
+
+  var EVENT_KEY = '.bs.select';
+
+  var classNames = {
+    DISABLED: 'disabled',
+    DIVIDER: 'divider',
+    SHOW: 'open',
+    DROPUP: 'dropup',
+    MENU: 'dropdown-menu',
+    MENURIGHT: 'dropdown-menu-right',
+    MENULEFT: 'dropdown-menu-left',
+    // to-do: replace with more advanced template/customization options
+    BUTTONCLASS: 'btn-default',
+    POPOVERHEADER: 'popover-title',
+    ICONBASE: 'glyphicon',
+    TICKICON: 'glyphicon-ok'
+  }
+
+  var Selector = {
+    MENU: '.' + classNames.MENU
+  }
+
+  var elementTemplates = {
+    span: document.createElement('span'),
+    i: document.createElement('i'),
+    subtext: document.createElement('small'),
+    a: document.createElement('a'),
+    li: document.createElement('li'),
+    whitespace: document.createTextNode('\u00A0'),
+    fragment: document.createDocumentFragment()
+  }
+
+  elementTemplates.a.setAttribute('role', 'option');
+  elementTemplates.subtext.className = 'text-muted';
+
+  elementTemplates.text = elementTemplates.span.cloneNode(false);
+  elementTemplates.text.className = 'text';
+
+  elementTemplates.checkMark = elementTemplates.span.cloneNode(false);
+
+  var REGEXP_ARROW = new RegExp(keyCodes.ARROW_UP + '|' + keyCodes.ARROW_DOWN);
+  var REGEXP_TAB_OR_ESCAPE = new RegExp('^' + keyCodes.TAB + '$|' + keyCodes.ESCAPE);
+
+  var generateOption = {
+    li: function (content, classes, optgroup) {
+      var li = elementTemplates.li.cloneNode(false);
+
+      if (content) {
+        if (content.nodeType === 1 || content.nodeType === 11) {
+          li.appendChild(content);
+        } else {
+          li.innerHTML = content;
+        }
+      }
+
+      if (typeof classes !== 'undefined' && classes !== '') li.className = classes;
+      if (typeof optgroup !== 'undefined' && optgroup !== null) li.classList.add('optgroup-' + optgroup);
+
+      return li;
+    },
+
+    a: function (text, classes, inline) {
+      var a = elementTemplates.a.cloneNode(true);
+
+      if (text) {
+        if (text.nodeType === 11) {
+          a.appendChild(text);
+        } else {
+          a.insertAdjacentHTML('beforeend', text);
+        }
+      }
+
+      if (typeof classes !== 'undefined' && classes !== '') a.className = classes;
+      if (version.major === '4') a.classList.add('dropdown-item');
+      if (inline) a.setAttribute('style', inline);
+
+      return a;
+    },
+
+    text: function (options, useFragment) {
+      var textElement = elementTemplates.text.cloneNode(false),
+          subtextElement,
+          iconElement;
+
+      if (options.content) {
+        textElement.innerHTML = options.content;
+      } else {
+        textElement.textContent = options.text;
+
+        if (options.icon) {
+          var whitespace = elementTemplates.whitespace.cloneNode(false);
+
+          // need to use <i> for icons in the button to prevent a breaking change
+          // note: switch to span in next major release
+          iconElement = (useFragment === true ? elementTemplates.i : elementTemplates.span).cloneNode(false);
+          iconElement.className = options.iconBase + ' ' + options.icon;
+
+          elementTemplates.fragment.appendChild(iconElement);
+          elementTemplates.fragment.appendChild(whitespace);
+        }
+
+        if (options.subtext) {
+          subtextElement = elementTemplates.subtext.cloneNode(false);
+          subtextElement.textContent = options.subtext;
+          textElement.appendChild(subtextElement);
+        }
+      }
+
+      if (useFragment === true) {
+        while (textElement.childNodes.length > 0) {
+          elementTemplates.fragment.appendChild(textElement.childNodes[0]);
+        }
+      } else {
+        elementTemplates.fragment.appendChild(textElement);
+      }
+
+      return elementTemplates.fragment;
+    },
+
+    label: function (options) {
+      var textElement = elementTemplates.text.cloneNode(false),
+          subtextElement,
+          iconElement;
+
+      textElement.innerHTML = options.label;
+
+      if (options.icon) {
+        var whitespace = elementTemplates.whitespace.cloneNode(false);
+
+        iconElement = elementTemplates.span.cloneNode(false);
+        iconElement.className = options.iconBase + ' ' + options.icon;
+
+        elementTemplates.fragment.appendChild(iconElement);
+        elementTemplates.fragment.appendChild(whitespace);
+      }
+
+      if (options.subtext) {
+        subtextElement = elementTemplates.subtext.cloneNode(false);
+        subtextElement.textContent = options.subtext;
+        textElement.appendChild(subtextElement);
+      }
+
+      elementTemplates.fragment.appendChild(textElement);
+
+      return elementTemplates.fragment;
+    }
+  }
+
+  var Selectpicker = function (element, options) {
+    var that = this;
+
+    // bootstrap-select has been initialized - revert valHooks.select.set back to its original function
+    if (!valHooks.useDefault) {
+      $.valHooks.select.set = valHooks._set;
+      valHooks.useDefault = true;
+    }
+
+    this.$element = $(element);
+    this.$newElement = null;
+    this.$button = null;
+    this.$menu = null;
+    this.options = options;
+    this.selectpicker = {
+      main: {},
+      search: {},
+      current: {}, // current changes if a search is in progress
+      view: {},
+      keydown: {
+        keyHistory: '',
+        resetKeyHistory: {
+          start: function () {
+            return setTimeout(function () {
+              that.selectpicker.keydown.keyHistory = '';
+            }, 800);
+          }
+        }
+      }
+    };
+    // If we have no title yet, try to pull it from the html title attribute (jQuery doesnt' pick it up as it's not a
+    // data-attribute)
+    if (this.options.title === null) {
+      this.options.title = this.$element.attr('title');
+    }
+
+    // Format window padding
+    var winPad = this.options.windowPadding;
+    if (typeof winPad === 'number') {
+      this.options.windowPadding = [winPad, winPad, winPad, winPad];
+    }
+
+    // Expose public methods
+    this.val = Selectpicker.prototype.val;
+    this.render = Selectpicker.prototype.render;
+    this.refresh = Selectpicker.prototype.refresh;
+    this.setStyle = Selectpicker.prototype.setStyle;
+    this.selectAll = Selectpicker.prototype.selectAll;
+    this.deselectAll = Selectpicker.prototype.deselectAll;
+    this.destroy = Selectpicker.prototype.destroy;
+    this.remove = Selectpicker.prototype.remove;
+    this.show = Selectpicker.prototype.show;
+    this.hide = Selectpicker.prototype.hide;
+
+    this.init();
+  };
+
+  Selectpicker.VERSION = '1.13.10';
+
+  // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both.
+  Selectpicker.DEFAULTS = {
+    noneSelectedText: 'Nothing selected',
+    noneResultsText: 'No results matched {0}',
+    countSelectedText: function (numSelected, numTotal) {
+      return (numSelected == 1) ? '{0} item selected' : '{0} items selected';
+    },
+    maxOptionsText: function (numAll, numGroup) {
+      return [
+        (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)',
+        (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)'
+      ];
+    },
+    selectAllText: 'Select All',
+    deselectAllText: 'Deselect All',
+    doneButton: false,
+    doneButtonText: 'Close',
+    multipleSeparator: ', ',
+    styleBase: 'btn',
+    style: classNames.BUTTONCLASS,
+    size: 'auto',
+    title: null,
+    selectedTextFormat: 'values',
+    width: false,
+    container: false,
+    hideDisabled: false,
+    showSubtext: false,
+    showIcon: true,
+    showContent: true,
+    dropupAuto: true,
+    header: false,
+    liveSearch: false,
+    liveSearchPlaceholder: null,
+    liveSearchNormalize: false,
+    liveSearchStyle: 'contains',
+    actionsBox: false,
+    iconBase: classNames.ICONBASE,
+    tickIcon: classNames.TICKICON,
+    showTick: false,
+    template: {
+      caret: '<span class="caret"></span>'
+    },
+    maxOptions: false,
+    mobile: false,
+    selectOnTab: false,
+    dropdownAlignRight: false,
+    windowPadding: 0,
+    virtualScroll: 600,
+    display: false,
+    sanitize: true,
+    sanitizeFn: null,
+    whiteList: DefaultWhitelist
+  };
+
+  Selectpicker.prototype = {
+
+    constructor: Selectpicker,
+
+    init: function () {
+      var that = this,
+          id = this.$element.attr('id');
+
+      selectId++;
+      this.selectId = 'bs-select-' + selectId;
+
+      this.$element[0].classList.add('bs-select-hidden');
+
+      this.multiple = this.$element.prop('multiple');
+      this.autofocus = this.$element.prop('autofocus');
+
+      if (this.$element[0].classList.contains('show-tick')) {
+        this.options.showTick = true;
+      }
+
+      this.$newElement = this.createDropdown();
+      this.$element
+        .after(this.$newElement)
+        .prependTo(this.$newElement);
+
+      this.$button = this.$newElement.children('button');
+      this.$menu = this.$newElement.children(Selector.MENU);
+      this.$menuInner = this.$menu.children('.inner');
+      this.$searchbox = this.$menu.find('input');
+
+      this.$element[0].classList.remove('bs-select-hidden');
+
+      if (this.options.dropdownAlignRight === true) this.$menu[0].classList.add(classNames.MENURIGHT);
+
+      if (typeof id !== 'undefined') {
+        this.$button.attr('data-id', id);
+      }
+
+      this.checkDisabled();
+      this.clickListener();
+
+      if (this.options.liveSearch) {
+        this.liveSearchListener();
+        this.focusedParent = this.$searchbox[0];
+      } else {
+        this.focusedParent = this.$menuInner[0];
+      }
+
+      this.setStyle();
+      this.render();
+      this.setWidth();
+      if (this.options.container) {
+        this.selectPosition();
+      } else {
+        this.$element.on('hide' + EVENT_KEY, function () {
+          if (that.isVirtual()) {
+            // empty menu on close
+            var menuInner = that.$menuInner[0],
+                emptyMenu = menuInner.firstChild.cloneNode(false);
+
+            // replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = ''
+            menuInner.replaceChild(emptyMenu, menuInner.firstChild);
+            menuInner.scrollTop = 0;
+          }
+        });
+      }
+      this.$menu.data('this', this);
+      this.$newElement.data('this', this);
+      if (this.options.mobile) this.mobile();
+
+      this.$newElement.on({
+        'hide.bs.dropdown': function (e) {
+          that.$element.trigger('hide' + EVENT_KEY, e);
+        },
+        'hidden.bs.dropdown': function (e) {
+          that.$element.trigger('hidden' + EVENT_KEY, e);
+        },
+        'show.bs.dropdown': function (e) {
+          that.$element.trigger('show' + EVENT_KEY, e);
+        },
+        'shown.bs.dropdown': function (e) {
+          that.$element.trigger('shown' + EVENT_KEY, e);
+        }
+      });
+
+      if (that.$element[0].hasAttribute('required')) {
+        this.$element.on('invalid' + EVENT_KEY, function () {
+          that.$button[0].classList.add('bs-invalid');
+
+          that.$element
+            .on('shown' + EVENT_KEY + '.invalid', function () {
+              that.$element
+                .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened
+                .off('shown' + EVENT_KEY + '.invalid');
+            })
+            .on('rendered' + EVENT_KEY, function () {
+              // if select is no longer invalid, remove the bs-invalid class
+              if (this.validity.valid) that.$button[0].classList.remove('bs-invalid');
+              that.$element.off('rendered' + EVENT_KEY);
+            });
+
+          that.$button.on('blur' + EVENT_KEY, function () {
+            that.$element.trigger('focus').trigger('blur');
+            that.$button.off('blur' + EVENT_KEY);
+          });
+        });
+      }
+
+      setTimeout(function () {
+        that.createLi();
+        that.$element.trigger('loaded' + EVENT_KEY);
+      });
+    },
+
+    createDropdown: function () {
+      // Options
+      // If we are multiple or showTick option is set, then add the show-tick class
+      var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '',
+          multiselectable = this.multiple ? ' aria-multiselectable="true"' : '',
+          inputGroup = '',
+          autofocus = this.autofocus ? ' autofocus' : '';
+
+      if (version.major < 4 && this.$element.parent().hasClass('input-group')) {
+        inputGroup = ' input-group-btn';
+      }
+
+      // Elements
+      var drop,
+          header = '',
+          searchbox = '',
+          actionsbox = '',
+          donebutton = '';
+
+      if (this.options.header) {
+        header =
+          '<div class="' + classNames.POPOVERHEADER + '">' +
+            '<button type="button" class="close" aria-hidden="true">&times;</button>' +
+              this.options.header +
+          '</div>';
+      }
+
+      if (this.options.liveSearch) {
+        searchbox =
+          '<div class="bs-searchbox">' +
+            '<input type="text" class="form-control" autocomplete="off"' +
+              (
+                this.options.liveSearchPlaceholder === null ? ''
+                :
+                ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"'
+              ) +
+              ' role="combobox" aria-label="Search" aria-controls="' + this.selectId + '" aria-autocomplete="list">' +
+          '</div>';
+      }
+
+      if (this.multiple && this.options.actionsBox) {
+        actionsbox =
+          '<div class="bs-actionsbox">' +
+            '<div class="btn-group btn-group-sm btn-block">' +
+              '<button type="button" class="actions-btn bs-select-all btn ' + classNames.BUTTONCLASS + '">' +
+                this.options.selectAllText +
+              '</button>' +
+              '<button type="button" class="actions-btn bs-deselect-all btn ' + classNames.BUTTONCLASS + '">' +
+                this.options.deselectAllText +
+              '</button>' +
+            '</div>' +
+          '</div>';
+      }
+
+      if (this.multiple && this.options.doneButton) {
+        donebutton =
+          '<div class="bs-donebutton">' +
+            '<div class="btn-group btn-block">' +
+              '<button type="button" class="btn btn-sm ' + classNames.BUTTONCLASS + '">' +
+                this.options.doneButtonText +
+              '</button>' +
+            '</div>' +
+          '</div>';
+      }
+
+      drop =
+        '<div class="dropdown bootstrap-select' + showTick + inputGroup + '">' +
+          '<button type="button" class="' + this.options.styleBase + ' dropdown-toggle" ' + (this.options.display === 'static' ? 'data-display="static"' : '') + 'data-toggle="dropdown"' + autofocus + ' role="combobox" aria-owns="' + this.selectId + '" aria-haspopup="listbox" aria-expanded="false">' +
+            '<div class="filter-option">' +
+              '<div class="filter-option-inner">' +
+                '<div class="filter-option-inner-inner"></div>' +
+              '</div> ' +
+            '</div>' +
+            (
+              version.major === '4' ? ''
+              :
+              '<span class="bs-caret">' +
+                this.options.template.caret +
+              '</span>'
+            ) +
+          '</button>' +
+          '<div class="' + classNames.MENU + ' ' + (version.major === '4' ? '' : classNames.SHOW) + '">' +
+            header +
+            searchbox +
+            actionsbox +
+            '<div class="inner ' + classNames.SHOW + '" role="listbox" id="' + this.selectId + '" tabindex="-1" ' + multiselectable + '>' +
+                '<ul class="' + classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : '') + '" role="presentation">' +
+                '</ul>' +
+            '</div>' +
+            donebutton +
+          '</div>' +
+        '</div>';
+
+      return $(drop);
+    },
+
+    setPositionData: function () {
+      this.selectpicker.view.canHighlight = [];
+      this.selectpicker.view.size = 0;
+
+      for (var i = 0; i < this.selectpicker.current.data.length; i++) {
+        var li = this.selectpicker.current.data[i],
+            canHighlight = true;
+
+        if (li.type === 'divider') {
+          canHighlight = false;
+          li.height = this.sizeInfo.dividerHeight;
+        } else if (li.type === 'optgroup-label') {
+          canHighlight = false;
+          li.height = this.sizeInfo.dropdownHeaderHeight;
+        } else {
+          li.height = this.sizeInfo.liHeight;
+        }
+
+        if (li.disabled) canHighlight = false;
+
+        this.selectpicker.view.canHighlight.push(canHighlight);
+
+        if (canHighlight) {
+          this.selectpicker.view.size++;
+          li.posinset = this.selectpicker.view.size;
+        }
+
+        li.position = (i === 0 ? 0 : this.selectpicker.current.data[i - 1].position) + li.height;
+      }
+    },
+
+    isVirtual: function () {
+      return (this.options.virtualScroll !== false) && (this.selectpicker.main.elements.length >= this.options.virtualScroll) || this.options.virtualScroll === true;
+    },
+
+    createView: function (isSearching, setSize, refresh) {
+      var that = this,
+          scrollTop = 0,
+          active = [],
+          selected,
+          prevActive;
+
+      this.selectpicker.current = isSearching ? this.selectpicker.search : this.selectpicker.main;
+
+      this.setPositionData();
+
+      if (setSize) {
+        if (refresh) {
+          scrollTop = this.$menuInner[0].scrollTop;
+        } else if (!that.multiple) {
+          var element = that.$element[0],
+              selectedIndex = (element.options[element.selectedIndex] || {}).liIndex;
+
+          if (typeof selectedIndex === 'number' && that.options.size !== false) {
+            var selectedData = that.selectpicker.main.data[selectedIndex],
+                position = selectedData && selectedData.position;
+
+            if (position) {
+              scrollTop = position - ((that.sizeInfo.menuInnerHeight + that.sizeInfo.liHeight) / 2);
+            }
+          }
+        }
+      }
+
+      scroll(scrollTop, true);
+
+      this.$menuInner.off('scroll.createView').on('scroll.createView', function (e, updateValue) {
+        if (!that.noScroll) scroll(this.scrollTop, updateValue);
+        that.noScroll = false;
+      });
+
+      function scroll (scrollTop, init) {
+        var size = that.selectpicker.current.elements.length,
+            chunks = [],
+            chunkSize,
+            chunkCount,
+            firstChunk,
+            lastChunk,
+            currentChunk,
+            prevPositions,
+            positionIsDifferent,
+            previousElements,
+            menuIsDifferent = true,
+            isVirtual = that.isVirtual();
+
+        that.selectpicker.view.scrollTop = scrollTop;
+
+        if (isVirtual === true) {
+          // if an option that is encountered that is wider than the current menu width, update the menu width accordingly
+          if (that.sizeInfo.hasScrollBar && that.$menu[0].offsetWidth > that.sizeInfo.totalMenuWidth) {
+            that.sizeInfo.menuWidth = that.$menu[0].offsetWidth;
+            that.sizeInfo.totalMenuWidth = that.sizeInfo.menuWidth + that.sizeInfo.scrollBarWidth;
+            that.$menu.css('min-width', that.sizeInfo.menuWidth);
+          }
+        }
+
+        chunkSize = Math.ceil(that.sizeInfo.menuInnerHeight / that.sizeInfo.liHeight * 1.5); // number of options in a chunk
+        chunkCount = Math.round(size / chunkSize) || 1; // number of chunks
+
+        for (var i = 0; i < chunkCount; i++) {
+          var endOfChunk = (i + 1) * chunkSize;
+
+          if (i === chunkCount - 1) {
+            endOfChunk = size;
+          }
+
+          chunks[i] = [
+            (i) * chunkSize + (!i ? 0 : 1),
+            endOfChunk
+          ];
+
+          if (!size) break;
+
+          if (currentChunk === undefined && scrollTop <= that.selectpicker.current.data[endOfChunk - 1].position - that.sizeInfo.menuInnerHeight) {
+            currentChunk = i;
+          }
+        }
+
+        if (currentChunk === undefined) currentChunk = 0;
+
+        prevPositions = [that.selectpicker.view.position0, that.selectpicker.view.position1];
+
+        // always display previous, current, and next chunks
+        firstChunk = Math.max(0, currentChunk - 1);
+        lastChunk = Math.min(chunkCount - 1, currentChunk + 1);
+
+        that.selectpicker.view.position0 = isVirtual === false ? 0 : (Math.max(0, chunks[firstChunk][0]) || 0);
+        that.selectpicker.view.position1 = isVirtual === false ? size : (Math.min(size, chunks[lastChunk][1]) || 0);
+
+        positionIsDifferent = prevPositions[0] !== that.selectpicker.view.position0 || prevPositions[1] !== that.selectpicker.view.position1;
+
+        if (that.activeIndex !== undefined) {
+          prevActive = that.selectpicker.main.elements[that.prevActiveIndex];
+          active = that.selectpicker.main.elements[that.activeIndex];
+          selected = that.selectpicker.main.elements[that.selectedIndex];
+
+          if (init) {
+            if (that.activeIndex !== that.selectedIndex) {
+              that.defocusItem(active);
+            }
+            that.activeIndex = undefined;
+          }
+
+          if (that.activeIndex && that.activeIndex !== that.selectedIndex) {
+            that.defocusItem(selected);
+          }
+        }
+
+        if (that.prevActiveIndex !== undefined && that.prevActiveIndex !== that.activeIndex && that.prevActiveIndex !== that.selectedIndex) {
+          that.defocusItem(prevActive);
+        }
+
+        if (init || positionIsDifferent) {
+          previousElements = that.selectpicker.view.visibleElements ? that.selectpicker.view.visibleElements.slice() : [];
+
+          if (isVirtual === false) {
+            that.selectpicker.view.visibleElements = that.selectpicker.current.elements;
+          } else {
+            that.selectpicker.view.visibleElements = that.selectpicker.current.elements.slice(that.selectpicker.view.position0, that.selectpicker.view.position1);
+          }
+
+          that.setOptionStatus();
+
+          // if searching, check to make sure the list has actually been updated before updating DOM
+          // this prevents unnecessary repaints
+          if (isSearching || (isVirtual === false && init)) menuIsDifferent = !isEqual(previousElements, that.selectpicker.view.visibleElements);
+
+          // if virtual scroll is disabled and not searching,
+          // menu should never need to be updated more than once
+          if ((init || isVirtual === true) && menuIsDifferent) {
+            var menuInner = that.$menuInner[0],
+                menuFragment = document.createDocumentFragment(),
+                emptyMenu = menuInner.firstChild.cloneNode(false),
+                marginTop,
+                marginBottom,
+                elements = that.selectpicker.view.visibleElements,
+                toSanitize = [];
+
+            // replace the existing UL with an empty one - this is faster than $.empty()
+            menuInner.replaceChild(emptyMenu, menuInner.firstChild);
+
+            for (var i = 0, visibleElementsLen = elements.length; i < visibleElementsLen; i++) {
+              var element = elements[i],
+                  elText,
+                  elementData;
+
+              if (that.options.sanitize) {
+                elText = element.lastChild;
+
+                if (elText) {
+                  elementData = that.selectpicker.current.data[i + that.selectpicker.view.position0];
+
+                  if (elementData && elementData.content && !elementData.sanitized) {
+                    toSanitize.push(elText);
+                    elementData.sanitized = true;
+                  }
+                }
+              }
+
+              menuFragment.appendChild(element);
+            }
+
+            if (that.options.sanitize && toSanitize.length) {
+              sanitizeHtml(toSanitize, that.options.whiteList, that.options.sanitizeFn);
+            }
+
+            if (isVirtual === true) {
+              marginTop = (that.selectpicker.view.position0 === 0 ? 0 : that.selectpicker.current.data[that.selectpicker.view.position0 - 1].position);
+              marginBottom = (that.selectpicker.view.position1 > size - 1 ? 0 : that.selectpicker.current.data[size - 1].position - that.selectpicker.current.data[that.selectpicker.view.position1 - 1].position);
+
+              menuInner.firstChild.style.marginTop = marginTop + 'px';
+              menuInner.firstChild.style.marginBottom = marginBottom + 'px';
+            } else {
+              menuInner.firstChild.style.marginTop = 0;
+              menuInner.firstChild.style.marginBottom = 0;
+            }
+
+            menuInner.firstChild.appendChild(menuFragment);
+          }
+        }
+
+        that.prevActiveIndex = that.activeIndex;
+
+        if (!that.options.liveSearch) {
+          that.$menuInner.trigger('focus');
+        } else if (isSearching && init) {
+          var index = 0,
+              newActive;
+
+          if (!that.selectpicker.view.canHighlight[index]) {
+            index = 1 + that.selectpicker.view.canHighlight.slice(1).indexOf(true);
+          }
+
+          newActive = that.selectpicker.view.visibleElements[index];
+
+          that.defocusItem(that.selectpicker.view.currentActive);
+
+          that.activeIndex = (that.selectpicker.current.data[index] || {}).index;
+
+          that.focusItem(newActive);
+        }
+      }
+
+      $(window)
+        .off('resize' + EVENT_KEY + '.' + this.selectId + '.createView')
+        .on('resize' + EVENT_KEY + '.' + this.selectId + '.createView', function () {
+          var isActive = that.$newElement.hasClass(classNames.SHOW);
+
+          if (isActive) scroll(that.$menuInner[0].scrollTop);
+        });
+    },
+
+    focusItem: function (li, liData, noStyle) {
+      if (li) {
+        liData = liData || this.selectpicker.main.data[this.activeIndex];
+        var a = li.firstChild;
+
+        if (a) {
+          a.setAttribute('aria-setsize', this.selectpicker.view.size);
+          a.setAttribute('aria-posinset', liData.posinset);
+
+          if (noStyle !== true) {
+            this.focusedParent.setAttribute('aria-activedescendant', a.id);
+            li.classList.add('active');
+            a.classList.add('active');
+          }
+        }
+      }
+    },
+
+    defocusItem: function (li) {
+      if (li) {
+        li.classList.remove('active');
+        if (li.firstChild) li.firstChild.classList.remove('active');
+      }
+    },
+
+    setPlaceholder: function () {
+      var updateIndex = false;
+
+      if (this.options.title && !this.multiple) {
+        if (!this.selectpicker.view.titleOption) this.selectpicker.view.titleOption = document.createElement('option');
+
+        // this option doesn't create a new <li> element, but does add a new option at the start,
+        // so startIndex should increase to prevent having to check every option for the bs-title-option class
+        updateIndex = true;
+
+        var element = this.$element[0],
+            isSelected = false,
+            titleNotAppended = !this.selectpicker.view.titleOption.parentNode;
+
+        if (titleNotAppended) {
+          // Use native JS to prepend option (faster)
+          this.selectpicker.view.titleOption.className = 'bs-title-option';
+          this.selectpicker.view.titleOption.value = '';
+
+          // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option.
+          // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs,
+          // if so, the select will have the data-selected attribute
+          var $opt = $(element.options[element.selectedIndex]);
+          isSelected = $opt.attr('selected') === undefined && this.$element.data('selected') === undefined;
+        }
+
+        if (titleNotAppended || this.selectpicker.view.titleOption.index !== 0) {
+          element.insertBefore(this.selectpicker.view.titleOption, element.firstChild);
+        }
+
+        // Set selected *after* appending to select,
+        // otherwise the option doesn't get selected in IE
+        // set using selectedIndex, as setting the selected attr to true here doesn't work in IE11
+        if (isSelected) element.selectedIndex = 0;
+      }
+
+      return updateIndex;
+    },
+
+    createLi: function () {
+      var that = this,
+          iconBase = this.options.iconBase,
+          optionSelector = ':not([hidden]):not([data-hidden="true"])',
+          mainElements = [],
+          mainData = [],
+          widestOptionLength = 0,
+          optID = 0,
+          startIndex = this.setPlaceholder() ? 1 : 0; // append the titleOption if necessary and skip the first option in the loop
+
+      if (this.options.hideDisabled) optionSelector += ':not(:disabled)';
+
+      if ((that.options.showTick || that.multiple) && !elementTemplates.checkMark.parentNode) {
+        elementTemplates.checkMark.className = iconBase + ' ' + that.options.tickIcon + ' check-mark';
+        elementTemplates.a.appendChild(elementTemplates.checkMark);
+      }
+
+      var selectOptions = this.$element[0].querySelectorAll('select > *' + optionSelector);
+
+      function addDivider (config) {
+        var previousData = mainData[mainData.length - 1];
+
+        // ensure optgroup doesn't create back-to-back dividers
+        if (
+          previousData &&
+          previousData.type === 'divider' &&
+          (previousData.optID || config.optID)
+        ) {
+          return;
+        }
+
+        config = config || {};
+        config.type = 'divider';
+
+        mainElements.push(
+          generateOption.li(
+            false,
+            classNames.DIVIDER,
+            (config.optID ? config.optID + 'div' : undefined)
+          )
+        );
+
+        mainData.push(config);
+      }
+
+      function addOption (option, config) {
+        config = config || {};
+
+        config.divider = option.getAttribute('data-divider') === 'true';
+
+        if (config.divider) {
+          addDivider({
+            optID: config.optID
+          });
+        } else {
+          var liIndex = mainData.length,
+              cssText = option.style.cssText,
+              inlineStyle = cssText ? htmlEscape(cssText) : '',
+              optionClass = (option.className || '') + (config.optgroupClass || '');
+
+          if (config.optID) optionClass = 'opt ' + optionClass;
+
+          config.text = option.textContent;
+
+          config.content = option.getAttribute('data-content');
+          config.tokens = option.getAttribute('data-tokens');
+          config.subtext = option.getAttribute('data-subtext');
+          config.icon = option.getAttribute('data-icon');
+          config.iconBase = iconBase;
+
+          var textElement = generateOption.text(config);
+          var liElement = generateOption.li(
+            generateOption.a(
+              textElement,
+              optionClass,
+              inlineStyle
+            ),
+            '',
+            config.optID
+          );
+
+          if (liElement.firstChild) {
+            liElement.firstChild.id = that.selectId + '-' + liIndex;
+          }
+
+          mainElements.push(liElement);
+
+          option.liIndex = liIndex;
+
+          config.display = config.content || config.text;
+          config.type = 'option';
+          config.index = liIndex;
+          config.option = option;
+          config.disabled = config.disabled || option.disabled;
+
+          mainData.push(config);
+
+          var combinedLength = 0;
+
+          // count the number of characters in the option - not perfect, but should work in most cases
+          if (config.display) combinedLength += config.display.length;
+          if (config.subtext) combinedLength += config.subtext.length;
+          // if there is an icon, ensure this option's width is checked
+          if (config.icon) combinedLength += 1;
+
+          if (combinedLength > widestOptionLength) {
+            widestOptionLength = combinedLength;
+
+            // guess which option is the widest
+            // use this when calculating menu width
+            // not perfect, but it's fast, and the width will be updating accordingly when scrolling
+            that.selectpicker.view.widestOption = mainElements[mainElements.length - 1];
+          }
+        }
+      }
+
+      function addOptgroup (index, selectOptions) {
+        var optgroup = selectOptions[index],
+            previous = selectOptions[index - 1],
+            next = selectOptions[index + 1],
+            options = optgroup.querySelectorAll('option' + optionSelector);
+
+        if (!options.length) return;
+
+        var config = {
+              label: htmlEscape(optgroup.label),
+              subtext: optgroup.getAttribute('data-subtext'),
+              icon: optgroup.getAttribute('data-icon'),
+              iconBase: iconBase
+            },
+            optgroupClass = ' ' + (optgroup.className || ''),
+            headerIndex,
+            lastIndex;
+
+        optID++;
+
+        if (previous) {
+          addDivider({ optID: optID });
+        }
+
+        var labelElement = generateOption.label(config);
+
+        mainElements.push(
+          generateOption.li(labelElement, 'dropdown-header' + optgroupClass, optID)
+        );
+
+        mainData.push({
+          display: config.label,
+          subtext: config.subtext,
+          type: 'optgroup-label',
+          optID: optID
+        });
+
+        for (var j = 0, len = options.length; j < len; j++) {
+          var option = options[j];
+
+          if (j === 0) {
+            headerIndex = mainData.length - 1;
+            lastIndex = headerIndex + len;
+          }
+
+          addOption(option, {
+            headerIndex: headerIndex,
+            lastIndex: lastIndex,
+            optID: optID,
+            optgroupClass: optgroupClass,
+            disabled: optgroup.disabled
+          });
+        }
+
+        if (next) {
+          addDivider({ optID: optID });
+        }
+      }
+
+      for (var len = selectOptions.length; startIndex < len; startIndex++) {
+        var item = selectOptions[startIndex];
+
+        if (item.tagName !== 'OPTGROUP') {
+          addOption(item, {});
+        } else {
+          addOptgroup(startIndex, selectOptions);
+        }
+      }
+
+      this.selectpicker.main.elements = mainElements;
+      this.selectpicker.main.data = mainData;
+
+      this.selectpicker.current = this.selectpicker.main;
+    },
+
+    findLis: function () {
+      return this.$menuInner.find('.inner > li');
+    },
+
+    render: function () {
+      // ensure titleOption is appended and selected (if necessary) before getting selectedOptions
+      this.setPlaceholder();
+
+      var that = this,
+          element = this.$element[0],
+          selectedOptions = getSelectedOptions(element, this.options.hideDisabled),
+          selectedCount = selectedOptions.length,
+          button = this.$button[0],
+          buttonInner = button.querySelector('.filter-option-inner-inner'),
+          multipleSeparator = document.createTextNode(this.options.multipleSeparator),
+          titleFragment = elementTemplates.fragment.cloneNode(false),
+          showCount,
+          countMax,
+          hasContent = false;
+
+      button.classList.toggle('bs-placeholder', that.multiple ? !selectedCount : !getSelectValues(element, selectedOptions));
+
+      this.tabIndex();
+
+      if (this.options.selectedTextFormat === 'static') {
+        titleFragment = generateOption.text({ text: this.options.title }, true);
+      } else {
+        showCount = this.multiple && this.options.selectedTextFormat.indexOf('count') !== -1 && selectedCount > 1;
+
+        // determine if the number of selected options will be shown (showCount === true)
+        if (showCount) {
+          countMax = this.options.selectedTextFormat.split('>');
+          showCount = (countMax.length > 1 && selectedCount > countMax[1]) || (countMax.length === 1 && selectedCount >= 2);
+        }
+
+        // only loop through all selected options if the count won't be shown
+        if (showCount === false) {
+          for (var selectedIndex = 0; selectedIndex < selectedCount; selectedIndex++) {
+            if (selectedIndex < 50) {
+              var option = selectedOptions[selectedIndex],
+                  titleOptions = {},
+                  thisData = {
+                    content: option.getAttribute('data-content'),
+                    subtext: option.getAttribute('data-subtext'),
+                    icon: option.getAttribute('data-icon')
+                  };
+
+              if (this.multiple && selectedIndex > 0) {
+                titleFragment.appendChild(multipleSeparator.cloneNode(false));
+              }
+
+              if (option.title) {
+                titleOptions.text = option.title;
+              } else if (thisData.content && that.options.showContent) {
+                titleOptions.content = thisData.content.toString();
+                hasContent = true;
+              } else {
+                if (that.options.showIcon) {
+                  titleOptions.icon = thisData.icon;
+                  titleOptions.iconBase = this.options.iconBase;
+                }
+                if (that.options.showSubtext && !that.multiple && thisData.subtext) titleOptions.subtext = ' ' + thisData.subtext;
+                titleOptions.text = option.textContent.trim();
+              }
+
+              titleFragment.appendChild(generateOption.text(titleOptions, true));
+            } else {
+              break;
+            }
+          }
+
+          // add ellipsis
+          if (selectedCount > 49) {
+            titleFragment.appendChild(document.createTextNode('...'));
+          }
+        } else {
+          var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])';
+          if (this.options.hideDisabled) optionSelector += ':not(:disabled)';
+
+          // If this is a multiselect, and selectedTextFormat is count, then show 1 of 2 selected, etc.
+          var totalCount = this.$element[0].querySelectorAll('select > option' + optionSelector + ', optgroup' + optionSelector + ' option' + optionSelector).length,
+              tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedCount, totalCount) : this.options.countSelectedText;
+
+          titleFragment = generateOption.text({
+            text: tr8nText.replace('{0}', selectedCount.toString()).replace('{1}', totalCount.toString())
+          }, true);
+        }
+      }
+
+      if (this.options.title == undefined) {
+        // use .attr to ensure undefined is returned if title attribute is not set
+        this.options.title = this.$element.attr('title');
+      }
+
+      // If the select doesn't have a title, then use the default, or if nothing is set at all, use noneSelectedText
+      if (!titleFragment.childNodes.length) {
+        titleFragment = generateOption.text({
+          text: typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText
+        }, true);
+      }
+
+      // strip all HTML tags and trim the result, then unescape any escaped tags
+      button.title = titleFragment.textContent.replace(/<[^>]*>?/g, '').trim();
+
+      if (this.options.sanitize && hasContent) {
+        sanitizeHtml([titleFragment], that.options.whiteList, that.options.sanitizeFn);
+      }
+
+      buttonInner.innerHTML = '';
+      buttonInner.appendChild(titleFragment);
+
+      if (version.major < 4 && this.$newElement[0].classList.contains('bs3-has-addon')) {
+        var filterExpand = button.querySelector('.filter-expand'),
+            clone = buttonInner.cloneNode(true);
+
+        clone.className = 'filter-expand';
+
+        if (filterExpand) {
+          button.replaceChild(clone, filterExpand);
+        } else {
+          button.appendChild(clone);
+        }
+      }
+
+      this.$element.trigger('rendered' + EVENT_KEY);
+    },
+
+    /**
+     * @param [style]
+     * @param [status]
+     */
+    setStyle: function (newStyle, status) {
+      var button = this.$button[0],
+          newElement = this.$newElement[0],
+          style = this.options.style.trim(),
+          buttonClass;
+
+      if (this.$element.attr('class')) {
+        this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, ''));
+      }
+
+      if (version.major < 4) {
+        newElement.classList.add('bs3');
+
+        if (newElement.parentNode.classList.contains('input-group') &&
+            (newElement.previousElementSibling || newElement.nextElementSibling) &&
+            (newElement.previousElementSibling || newElement.nextElementSibling).classList.contains('input-group-addon')
+        ) {
+          newElement.classList.add('bs3-has-addon');
+        }
+      }
+
+      if (newStyle) {
+        buttonClass = newStyle.trim();
+      } else {
+        buttonClass = style;
+      }
+
+      if (status == 'add') {
+        if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' '));
+      } else if (status == 'remove') {
+        if (buttonClass) button.classList.remove.apply(button.classList, buttonClass.split(' '));
+      } else {
+        if (style) button.classList.remove.apply(button.classList, style.split(' '));
+        if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' '));
+      }
+    },
+
+    liHeight: function (refresh) {
+      if (!refresh && (this.options.size === false || this.sizeInfo)) return;
+
+      if (!this.sizeInfo) this.sizeInfo = {};
+
+      var newElement = document.createElement('div'),
+          menu = document.createElement('div'),
+          menuInner = document.createElement('div'),
+          menuInnerInner = document.createElement('ul'),
+          divider = document.createElement('li'),
+          dropdownHeader = document.createElement('li'),
+          li = document.createElement('li'),
+          a = document.createElement('a'),
+          text = document.createElement('span'),
+          header = this.options.header && this.$menu.find('.' + classNames.POPOVERHEADER).length > 0 ? this.$menu.find('.' + classNames.POPOVERHEADER)[0].cloneNode(true) : null,
+          search = this.options.liveSearch ? document.createElement('div') : null,
+          actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null,
+          doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null,
+          firstOption = this.$element.find('option')[0];
+
+      this.sizeInfo.selectWidth = this.$newElement[0].offsetWidth;
+
+      text.className = 'text';
+      a.className = 'dropdown-item ' + (firstOption ? firstOption.className : '');
+      newElement.className = this.$menu[0].parentNode.className + ' ' + classNames.SHOW;
+      newElement.style.width = this.sizeInfo.selectWidth + 'px';
+      if (this.options.width === 'auto') menu.style.minWidth = 0;
+      menu.className = classNames.MENU + ' ' + classNames.SHOW;
+      menuInner.className = 'inner ' + classNames.SHOW;
+      menuInnerInner.className = classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : '');
+      divider.className = classNames.DIVIDER;
+      dropdownHeader.className = 'dropdown-header';
+
+      text.appendChild(document.createTextNode('\u200b'));
+      a.appendChild(text);
+      li.appendChild(a);
+      dropdownHeader.appendChild(text.cloneNode(true));
+
+      if (this.selectpicker.view.widestOption) {
+        menuInnerInner.appendChild(this.selectpicker.view.widestOption.cloneNode(true));
+      }
+
+      menuInnerInner.appendChild(li);
+      menuInnerInner.appendChild(divider);
+      menuInnerInner.appendChild(dropdownHeader);
+      if (header) menu.appendChild(header);
+      if (search) {
+        var input = document.createElement('input');
+        search.className = 'bs-searchbox';
+        input.className = 'form-control';
+        search.appendChild(input);
+        menu.appendChild(search);
+      }
+      if (actions) menu.appendChild(actions);
+      menuInner.appendChild(menuInnerInner);
+      menu.appendChild(menuInner);
+      if (doneButton) menu.appendChild(doneButton);
+      newElement.appendChild(menu);
+
+      document.body.appendChild(newElement);
+
+      var liHeight = li.offsetHeight,
+          dropdownHeaderHeight = dropdownHeader ? dropdownHeader.offsetHeight : 0,
+          headerHeight = header ? header.offsetHeight : 0,
+          searchHeight = search ? search.offsetHeight : 0,
+          actionsHeight = actions ? actions.offsetHeight : 0,
+          doneButtonHeight = doneButton ? doneButton.offsetHeight : 0,
+          dividerHeight = $(divider).outerHeight(true),
+          // fall back to jQuery if getComputedStyle is not supported
+          menuStyle = window.getComputedStyle ? window.getComputedStyle(menu) : false,
+          menuWidth = menu.offsetWidth,
+          $menu = menuStyle ? null : $(menu),
+          menuPadding = {
+            vert: toInteger(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) +
+                  toInteger(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) +
+                  toInteger(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) +
+                  toInteger(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')),
+            horiz: toInteger(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) +
+                  toInteger(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) +
+                  toInteger(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) +
+                  toInteger(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth'))
+          },
+          menuExtras = {
+            vert: menuPadding.vert +
+                  toInteger(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) +
+                  toInteger(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2,
+            horiz: menuPadding.horiz +
+                  toInteger(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) +
+                  toInteger(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2
+          },
+          scrollBarWidth;
+
+      menuInner.style.overflowY = 'scroll';
+
+      scrollBarWidth = menu.offsetWidth - menuWidth;
+
+      document.body.removeChild(newElement);
+
+      this.sizeInfo.liHeight = liHeight;
+      this.sizeInfo.dropdownHeaderHeight = dropdownHeaderHeight;
+      this.sizeInfo.headerHeight = headerHeight;
+      this.sizeInfo.searchHeight = searchHeight;
+      this.sizeInfo.actionsHeight = actionsHeight;
+      this.sizeInfo.doneButtonHeight = doneButtonHeight;
+      this.sizeInfo.dividerHeight = dividerHeight;
+      this.sizeInfo.menuPadding = menuPadding;
+      this.sizeInfo.menuExtras = menuExtras;
+      this.sizeInfo.menuWidth = menuWidth;
+      this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth;
+      this.sizeInfo.scrollBarWidth = scrollBarWidth;
+      this.sizeInfo.selectHeight = this.$newElement[0].offsetHeight;
+
+      this.setPositionData();
+    },
+
+    getSelectPosition: function () {
+      var that = this,
+          $window = $(window),
+          pos = that.$newElement.offset(),
+          $container = $(that.options.container),
+          containerPos;
+
+      if (that.options.container && $container.length && !$container.is('body')) {
+        containerPos = $container.offset();
+        containerPos.top += parseInt($container.css('borderTopWidth'));
+        containerPos.left += parseInt($container.css('borderLeftWidth'));
+      } else {
+        containerPos = { top: 0, left: 0 };
+      }
+
+      var winPad = that.options.windowPadding;
+
+      this.sizeInfo.selectOffsetTop = pos.top - containerPos.top - $window.scrollTop();
+      this.sizeInfo.selectOffsetBot = $window.height() - this.sizeInfo.selectOffsetTop - this.sizeInfo.selectHeight - containerPos.top - winPad[2];
+      this.sizeInfo.selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft();
+      this.sizeInfo.selectOffsetRight = $window.width() - this.sizeInfo.selectOffsetLeft - this.sizeInfo.selectWidth - containerPos.left - winPad[1];
+      this.sizeInfo.selectOffsetTop -= winPad[0];
+      this.sizeInfo.selectOffsetLeft -= winPad[3];
+    },
+
+    setMenuSize: function (isAuto) {
+      this.getSelectPosition();
+
+      var selectWidth = this.sizeInfo.selectWidth,
+          liHeight = this.sizeInfo.liHeight,
+          headerHeight = this.sizeInfo.headerHeight,
+          searchHeight = this.sizeInfo.searchHeight,
+          actionsHeight = this.sizeInfo.actionsHeight,
+          doneButtonHeight = this.sizeInfo.doneButtonHeight,
+          divHeight = this.sizeInfo.dividerHeight,
+          menuPadding = this.sizeInfo.menuPadding,
+          menuInnerHeight,
+          menuHeight,
+          divLength = 0,
+          minHeight,
+          _minHeight,
+          maxHeight,
+          menuInnerMinHeight,
+          estimate;
+
+      if (this.options.dropupAuto) {
+        // Get the estimated height of the menu without scrollbars.
+        // This is useful for smaller menus, where there might be plenty of room
+        // below the button without setting dropup, but we can't know
+        // the exact height of the menu until createView is called later
+        estimate = liHeight * this.selectpicker.current.elements.length + menuPadding.vert;
+        this.$newElement.toggleClass(classNames.DROPUP, this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot);
+      }
+
+      if (this.options.size === 'auto') {
+        _minHeight = this.selectpicker.current.elements.length > 3 ? this.sizeInfo.liHeight * 3 + this.sizeInfo.menuExtras.vert - 2 : 0;
+        menuHeight = this.sizeInfo.selectOffsetBot - this.sizeInfo.menuExtras.vert;
+        minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight;
+        menuInnerMinHeight = Math.max(_minHeight - menuPadding.vert, 0);
+
+        if (this.$newElement.hasClass(classNames.DROPUP)) {
+          menuHeight = this.sizeInfo.selectOffsetTop - this.sizeInfo.menuExtras.vert;
+        }
+
+        maxHeight = menuHeight;
+        menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert;
+      } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) {
+        for (var i = 0; i < this.options.size; i++) {
+          if (this.selectpicker.current.data[i].type === 'divider') divLength++;
+        }
+
+        menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert;
+        menuInnerHeight = menuHeight - menuPadding.vert;
+        maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight;
+        minHeight = menuInnerMinHeight = '';
+      }
+
+      if (this.options.dropdownAlignRight === 'auto') {
+        this.$menu.toggleClass(classNames.MENURIGHT, this.sizeInfo.selectOffsetLeft > this.sizeInfo.selectOffsetRight && this.sizeInfo.selectOffsetRight < (this.sizeInfo.totalMenuWidth - selectWidth));
+      }
+
+      this.$menu.css({
+        'max-height': maxHeight + 'px',
+        'overflow': 'hidden',
+        'min-height': minHeight + 'px'
+      });
+
+      this.$menuInner.css({
+        'max-height': menuInnerHeight + 'px',
+        'overflow-y': 'auto',
+        'min-height': menuInnerMinHeight + 'px'
+      });
+
+      // ensure menuInnerHeight is always a positive number to prevent issues calculating chunkSize in createView
+      this.sizeInfo.menuInnerHeight = Math.max(menuInnerHeight, 1);
+
+      if (this.selectpicker.current.data.length && this.selectpicker.current.data[this.selectpicker.current.data.length - 1].position > this.sizeInfo.menuInnerHeight) {
+        this.sizeInfo.hasScrollBar = true;
+        this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth + this.sizeInfo.scrollBarWidth;
+
+        this.$menu.css('min-width', this.sizeInfo.totalMenuWidth);
+      }
+
+      if (this.dropdown && this.dropdown._popper) this.dropdown._popper.update();
+    },
+
+    setSize: function (refresh) {
+      this.liHeight(refresh);
+
+      if (this.options.header) this.$menu.css('padding-top', 0);
+      if (this.options.size === false) return;
+
+      var that = this,
+          $window = $(window);
+
+      this.setMenuSize();
+
+      if (this.options.liveSearch) {
+        this.$searchbox
+          .off('input.setMenuSize propertychange.setMenuSize')
+          .on('input.setMenuSize propertychange.setMenuSize', function () {
+            return that.setMenuSize();
+          });
+      }
+
+      if (this.options.size === 'auto') {
+        $window
+          .off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize')
+          .on('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize', function () {
+            return that.setMenuSize();
+          });
+      } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) {
+        $window.off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize');
+      }
+
+      that.createView(false, true, refresh);
+    },
+
+    setWidth: function () {
+      var that = this;
+
+      if (this.options.width === 'auto') {
+        requestAnimationFrame(function () {
+          that.$menu.css('min-width', '0');
+
+          that.$element.on('loaded' + EVENT_KEY, function () {
+            that.liHeight();
+            that.setMenuSize();
+
+            // Get correct width if element is hidden
+            var $selectClone = that.$newElement.clone().appendTo('body'),
+                btnWidth = $selectClone.css('width', 'auto').children('button').outerWidth();
+
+            $selectClone.remove();
+
+            // Set width to whatever's larger, button title or longest option
+            that.sizeInfo.selectWidth = Math.max(that.sizeInfo.totalMenuWidth, btnWidth);
+            that.$newElement.css('width', that.sizeInfo.selectWidth + 'px');
+          });
+        });
+      } else if (this.options.width === 'fit') {
+        // Remove inline min-width so width can be changed from 'auto'
+        this.$menu.css('min-width', '');
+        this.$newElement.css('width', '').addClass('fit-width');
+      } else if (this.options.width) {
+        // Remove inline min-width so width can be changed from 'auto'
+        this.$menu.css('min-width', '');
+        this.$newElement.css('width', this.options.width);
+      } else {
+        // Remove inline min-width/width so width can be changed
+        this.$menu.css('min-width', '');
+        this.$newElement.css('width', '');
+      }
+      // Remove fit-width class if width is changed programmatically
+      if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') {
+        this.$newElement[0].classList.remove('fit-width');
+      }
+    },
+
+    selectPosition: function () {
+      this.$bsContainer = $('<div class="bs-container" />');
+
+      var that = this,
+          $container = $(this.options.container),
+          pos,
+          containerPos,
+          actualHeight,
+          getPlacement = function ($element) {
+            var containerPosition = {},
+                // fall back to dropdown's default display setting if display is not manually set
+                display = that.options.display || (
+                  // Bootstrap 3 doesn't have $.fn.dropdown.Constructor.Default
+                  $.fn.dropdown.Constructor.Default ? $.fn.dropdown.Constructor.Default.display
+                  : false
+                );
+
+            that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass(classNames.DROPUP, $element.hasClass(classNames.DROPUP));
+            pos = $element.offset();
+
+            if (!$container.is('body')) {
+              containerPos = $container.offset();
+              containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop();
+              containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft();
+            } else {
+              containerPos = { top: 0, left: 0 };
+            }
+
+            actualHeight = $element.hasClass(classNames.DROPUP) ? 0 : $element[0].offsetHeight;
+
+            // Bootstrap 4+ uses Popper for menu positioning
+            if (version.major < 4 || display === 'static') {
+              containerPosition.top = pos.top - containerPos.top + actualHeight;
+              containerPosition.left = pos.left - containerPos.left;
+            }
+
+            containerPosition.width = $element[0].offsetWidth;
+
+            that.$bsContainer.css(containerPosition);
+          };
+
+      this.$button.on('click.bs.dropdown.data-api', function () {
+        if (that.isDisabled()) {
+          return;
+        }
+
+        getPlacement(that.$newElement);
+
+        that.$bsContainer
+          .appendTo(that.options.container)
+          .toggleClass(classNames.SHOW, !that.$button.hasClass(classNames.SHOW))
+          .append(that.$menu);
+      });
+
+      $(window)
+        .off('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId)
+        .on('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId, function () {
+          var isActive = that.$newElement.hasClass(classNames.SHOW);
+
+          if (isActive) getPlacement(that.$newElement);
+        });
+
+      this.$element.on('hide' + EVENT_KEY, function () {
+        that.$menu.data('height', that.$menu.height());
+        that.$bsContainer.detach();
+      });
+    },
+
+    setOptionStatus: function (selectedOnly) {
+      var that = this;
+
+      that.noScroll = false;
+
+      if (that.selectpicker.view.visibleElements && that.selectpicker.view.visibleElements.length) {
+        for (var i = 0; i < that.selectpicker.view.visibleElements.length; i++) {
+          var liData = that.selectpicker.current.data[i + that.selectpicker.view.position0],
+              option = liData.option;
+
+          if (option) {
+            if (selectedOnly !== true) {
+              that.setDisabled(
+                liData.index,
+                liData.disabled
+              );
+            }
+
+            that.setSelected(
+              liData.index,
+              option.selected
+            );
+          }
+        }
+      }
+    },
+
+    /**
+     * @param {number} index - the index of the option that is being changed
+     * @param {boolean} selected - true if the option is being selected, false if being deselected
+     */
+    setSelected: function (index, selected) {
+      var li = this.selectpicker.main.elements[index],
+          liData = this.selectpicker.main.data[index],
+          activeIndexIsSet = this.activeIndex !== undefined,
+          thisIsActive = this.activeIndex === index,
+          prevActive,
+          a,
+          // if current option is already active
+          // OR
+          // if the current option is being selected, it's NOT multiple, and
+          // activeIndex is undefined:
+          //  - when the menu is first being opened, OR
+          //  - after a search has been performed, OR
+          //  - when retainActive is false when selecting a new option (i.e. index of the newly selected option is not the same as the current activeIndex)
+          keepActive = thisIsActive || (selected && !this.multiple && !activeIndexIsSet);
+
+      liData.selected = selected;
+
+      a = li.firstChild;
+
+      if (selected) {
+        this.selectedIndex = index;
+      }
+
+      li.classList.toggle('selected', selected);
+
+      if (keepActive) {
+        this.focusItem(li, liData);
+        this.selectpicker.view.currentActive = li;
+        this.activeIndex = index;
+      } else {
+        this.defocusItem(li);
+      }
+
+      if (a) {
+        a.classList.toggle('selected', selected);
+
+        if (selected) {
+          a.setAttribute('aria-selected', true);
+        } else {
+          if (this.multiple) {
+            a.setAttribute('aria-selected', false);
+          } else {
+            a.removeAttribute('aria-selected');
+          }
+        }
+      }
+
+      if (!keepActive && !activeIndexIsSet && selected && this.prevActiveIndex !== undefined) {
+        prevActive = this.selectpicker.main.elements[this.prevActiveIndex];
+
+        this.defocusItem(prevActive);
+      }
+    },
+
+    /**
+     * @param {number} index - the index of the option that is being disabled
+     * @param {boolean} disabled - true if the option is being disabled, false if being enabled
+     */
+    setDisabled: function (index, disabled) {
+      var li = this.selectpicker.main.elements[index],
+          a;
+
+      this.selectpicker.main.data[index].disabled = disabled;
+
+      a = li.firstChild;
+
+      li.classList.toggle(classNames.DISABLED, disabled);
+
+      if (a) {
+        if (version.major === '4') a.classList.toggle(classNames.DISABLED, disabled);
+
+        if (disabled) {
+          a.setAttribute('aria-disabled', disabled);
+          a.setAttribute('tabindex', -1);
+        } else {
+          a.removeAttribute('aria-disabled');
+          a.setAttribute('tabindex', 0);
+        }
+      }
+    },
+
+    isDisabled: function () {
+      return this.$element[0].disabled;
+    },
+
+    checkDisabled: function () {
+      var that = this;
+
+      if (this.isDisabled()) {
+        this.$newElement[0].classList.add(classNames.DISABLED);
+        this.$button.addClass(classNames.DISABLED).attr('tabindex', -1).attr('aria-disabled', true);
+      } else {
+        if (this.$button[0].classList.contains(classNames.DISABLED)) {
+          this.$newElement[0].classList.remove(classNames.DISABLED);
+          this.$button.removeClass(classNames.DISABLED).attr('aria-disabled', false);
+        }
+
+        if (this.$button.attr('tabindex') == -1 && !this.$element.data('tabindex')) {
+          this.$button.removeAttr('tabindex');
+        }
+      }
+
+      this.$button.on('click', function () {
+        return !that.isDisabled();
+      });
+    },
+
+    tabIndex: function () {
+      if (this.$element.data('tabindex') !== this.$element.attr('tabindex') &&
+        (this.$element.attr('tabindex') !== -98 && this.$element.attr('tabindex') !== '-98')) {
+        this.$element.data('tabindex', this.$element.attr('tabindex'));
+        this.$button.attr('tabindex', this.$element.data('tabindex'));
+      }
+
+      this.$element.attr('tabindex', -98);
+    },
+
+    clickListener: function () {
+      var that = this,
+          $document = $(document);
+
+      $document.data('spaceSelect', false);
+
+      this.$button.on('keyup', function (e) {
+        if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) {
+          e.preventDefault();
+          $document.data('spaceSelect', false);
+        }
+      });
+
+      this.$newElement.on('show.bs.dropdown', function () {
+        if (version.major > 3 && !that.dropdown) {
+          that.dropdown = that.$button.data('bs.dropdown');
+          that.dropdown._menu = that.$menu[0];
+        }
+      });
+
+      this.$button.on('click.bs.dropdown.data-api', function () {
+        if (!that.$newElement.hasClass(classNames.SHOW)) {
+          that.setSize();
+        }
+      });
+
+      function setFocus () {
+        if (that.options.liveSearch) {
+          that.$searchbox.trigger('focus');
+        } else {
+          that.$menuInner.trigger('focus');
+        }
+      }
+
+      function checkPopperExists () {
+        if (that.dropdown && that.dropdown._popper && that.dropdown._popper.state.isCreated) {
+          setFocus();
+        } else {
+          requestAnimationFrame(checkPopperExists);
+        }
+      }
+
+      this.$element.on('shown' + EVENT_KEY, function () {
+        if (that.$menuInner[0].scrollTop !== that.selectpicker.view.scrollTop) {
+          that.$menuInner[0].scrollTop = that.selectpicker.view.scrollTop;
+        }
+
+        if (version.major > 3) {
+          requestAnimationFrame(checkPopperExists);
+        } else {
+          setFocus();
+        }
+      });
+
+      // ensure posinset and setsize are correct before selecting an option via a click
+      this.$menuInner.on('mouseenter', 'li a', function (e) {
+        var hoverLi = this.parentElement,
+            position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0,
+            index = Array.prototype.indexOf.call(hoverLi.parentElement.children, hoverLi),
+            hoverData = that.selectpicker.current.data[index + position0];
+
+        that.focusItem(hoverLi, hoverData, true);
+      });
+
+      this.$menuInner.on('click', 'li a', function (e, retainActive) {
+        var $this = $(this),
+            element = that.$element[0],
+            position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0,
+            clickedData = that.selectpicker.current.data[$this.parent().index() + position0],
+            clickedIndex = clickedData.index,
+            prevValue = getSelectValues(element),
+            prevIndex = element.selectedIndex,
+            prevOption = element.options[prevIndex],
+            triggerChange = true;
+
+        // Don't close on multi choice menu
+        if (that.multiple && that.options.maxOptions !== 1) {
+          e.stopPropagation();
+        }
+
+        e.preventDefault();
+
+        // Don't run if the select is disabled
+        if (!that.isDisabled() && !$this.parent().hasClass(classNames.DISABLED)) {
+          var $options = that.$element.find('option'),
+              option = clickedData.option,
+              $option = $(option),
+              state = option.selected,
+              $optgroup = $option.parent('optgroup'),
+              $optgroupOptions = $optgroup.find('option'),
+              maxOptions = that.options.maxOptions,
+              maxOptionsGrp = $optgroup.data('maxOptions') || false;
+
+          if (clickedIndex === that.activeIndex) retainActive = true;
+
+          if (!retainActive) {
+            that.prevActiveIndex = that.activeIndex;
+            that.activeIndex = undefined;
+          }
+
+          if (!that.multiple) { // Deselect all others if not multi select box
+            prevOption.selected = false;
+            option.selected = true;
+            that.setSelected(clickedIndex, true);
+          } else { // Toggle the one we have chosen if we are multi select.
+            option.selected = !state;
+
+            that.setSelected(clickedIndex, !state);
+            $this.trigger('blur');
+
+            if (maxOptions !== false || maxOptionsGrp !== false) {
+              var maxReached = maxOptions < $options.filter(':selected').length,
+                  maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length;
+
+              if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) {
+                if (maxOptions && maxOptions == 1) {
+                  $options.prop('selected', false);
+                  $option.prop('selected', true);
+
+                  for (var i = 0; i < $options.length; i++) {
+                    that.setSelected(i, false);
+                  }
+
+                  that.setSelected(clickedIndex, true);
+                } else if (maxOptionsGrp && maxOptionsGrp == 1) {
+                  $optgroup.find('option:selected').prop('selected', false);
+                  $option.prop('selected', true);
+
+                  for (var i = 0; i < $optgroupOptions.length; i++) {
+                    var option = $optgroupOptions[i];
+                    that.setSelected($options.index(option), false);
+                  }
+
+                  that.setSelected(clickedIndex, true);
+                } else {
+                  var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText,
+                      maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText,
+                      maxTxt = maxOptionsArr[0].replace('{n}', maxOptions),
+                      maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp),
+                      $notify = $('<div class="notify"></div>');
+                  // If {var} is set in array, replace it
+                  /** @deprecated */
+                  if (maxOptionsArr[2]) {
+                    maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]);
+                    maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]);
+                  }
+
+                  $option.prop('selected', false);
+
+                  that.$menu.append($notify);
+
+                  if (maxOptions && maxReached) {
+                    $notify.append($('<div>' + maxTxt + '</div>'));
+                    triggerChange = false;
+                    that.$element.trigger('maxReached' + EVENT_KEY);
+                  }
+
+                  if (maxOptionsGrp && maxReachedGrp) {
+                    $notify.append($('<div>' + maxTxtGrp + '</div>'));
+                    triggerChange = false;
+                    that.$element.trigger('maxReachedGrp' + EVENT_KEY);
+                  }
+
+                  setTimeout(function () {
+                    that.setSelected(clickedIndex, false);
+                  }, 10);
+
+                  $notify.delay(750).fadeOut(300, function () {
+                    $(this).remove();
+                  });
+                }
+              }
+            }
+          }
+
+          if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) {
+            that.$button.trigger('focus');
+          } else if (that.options.liveSearch) {
+            that.$searchbox.trigger('focus');
+          }
+
+          // Trigger select 'change'
+          if (triggerChange) {
+            if (that.multiple || prevIndex !== element.selectedIndex) {
+              // $option.prop('selected') is current option state (selected/unselected). prevValue is the value of the select prior to being changed.
+              changedArguments = [option.index, $option.prop('selected'), prevValue];
+              that.$element
+                .triggerNative('change');
+            }
+          }
+        }
+      });
+
+      this.$menu.on('click', 'li.' + classNames.DISABLED + ' a, .' + classNames.POPOVERHEADER + ', .' + classNames.POPOVERHEADER + ' :not(.close)', function (e) {
+        if (e.currentTarget == this) {
+          e.preventDefault();
+          e.stopPropagation();
+          if (that.options.liveSearch && !$(e.target).hasClass('close')) {
+            that.$searchbox.trigger('focus');
+          } else {
+            that.$button.trigger('focus');
+          }
+        }
+      });
+
+      this.$menuInner.on('click', '.divider, .dropdown-header', function (e) {
+        e.preventDefault();
+        e.stopPropagation();
+        if (that.options.liveSearch) {
+          that.$searchbox.trigger('focus');
+        } else {
+          that.$button.trigger('focus');
+        }
+      });
+
+      this.$menu.on('click', '.' + classNames.POPOVERHEADER + ' .close', function () {
+        that.$button.trigger('click');
+      });
+
+      this.$searchbox.on('click', function (e) {
+        e.stopPropagation();
+      });
+
+      this.$menu.on('click', '.actions-btn', function (e) {
+        if (that.options.liveSearch) {
+          that.$searchbox.trigger('focus');
+        } else {
+          that.$button.trigger('focus');
+        }
+
+        e.preventDefault();
+        e.stopPropagation();
+
+        if ($(this).hasClass('bs-select-all')) {
+          that.selectAll();
+        } else {
+          that.deselectAll();
+        }
+      });
+
+      this.$element
+        .on('change' + EVENT_KEY, function () {
+          that.render();
+          that.$element.trigger('changed' + EVENT_KEY, changedArguments);
+          changedArguments = null;
+        })
+        .on('focus' + EVENT_KEY, function () {
+          if (!that.options.mobile) that.$button.trigger('focus');
+        });
+    },
+
+    liveSearchListener: function () {
+      var that = this,
+          noResults = document.createElement('li');
+
+      this.$button.on('click.bs.dropdown.data-api', function () {
+        if (!!that.$searchbox.val()) {
+          that.$searchbox.val('');
+        }
+      });
+
+      this.$searchbox.on('click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api', function (e) {
+        e.stopPropagation();
+      });
+
+      this.$searchbox.on('input propertychange', function () {
+        var searchValue = that.$searchbox.val();
+
+        that.selectpicker.search.elements = [];
+        that.selectpicker.search.data = [];
+
+        if (searchValue) {
+          var i,
+              searchMatch = [],
+              q = searchValue.toUpperCase(),
+              cache = {},
+              cacheArr = [],
+              searchStyle = that._searchStyle(),
+              normalizeSearch = that.options.liveSearchNormalize;
+
+          if (normalizeSearch) q = normalizeToBase(q);
+
+          that._$lisSelected = that.$menuInner.find('.selected');
+
+          for (var i = 0; i < that.selectpicker.main.data.length; i++) {
+            var li = that.selectpicker.main.data[i];
+
+            if (!cache[i]) {
+              cache[i] = stringSearch(li, q, searchStyle, normalizeSearch);
+            }
+
+            if (cache[i] && li.headerIndex !== undefined && cacheArr.indexOf(li.headerIndex) === -1) {
+              if (li.headerIndex > 0) {
+                cache[li.headerIndex - 1] = true;
+                cacheArr.push(li.headerIndex - 1);
+              }
+
+              cache[li.headerIndex] = true;
+              cacheArr.push(li.headerIndex);
+
+              cache[li.lastIndex + 1] = true;
+            }
+
+            if (cache[i] && li.type !== 'optgroup-label') cacheArr.push(i);
+          }
+
+          for (var i = 0, cacheLen = cacheArr.length; i < cacheLen; i++) {
+            var index = cacheArr[i],
+                prevIndex = cacheArr[i - 1],
+                li = that.selectpicker.main.data[index],
+                liPrev = that.selectpicker.main.data[prevIndex];
+
+            if (li.type !== 'divider' || (li.type === 'divider' && liPrev && liPrev.type !== 'divider' && cacheLen - 1 !== i)) {
+              that.selectpicker.search.data.push(li);
+              searchMatch.push(that.selectpicker.main.elements[index]);
+            }
+          }
+
+          that.activeIndex = undefined;
+          that.noScroll = true;
+          that.$menuInner.scrollTop(0);
+          that.selectpicker.search.elements = searchMatch;
+          that.createView(true);
+
+          if (!searchMatch.length) {
+            noResults.className = 'no-results';
+            noResults.innerHTML = that.options.noneResultsText.replace('{0}', '"' + htmlEscape(searchValue) + '"');
+            that.$menuInner[0].firstChild.appendChild(noResults);
+          }
+        } else {
+          that.$menuInner.scrollTop(0);
+          that.createView(false);
+        }
+      });
+    },
+
+    _searchStyle: function () {
+      return this.options.liveSearchStyle || 'contains';
+    },
+
+    val: function (value) {
+      var element = this.$element[0];
+
+      if (typeof value !== 'undefined') {
+        var prevValue = getSelectValues(element);
+
+        changedArguments = [null, null, prevValue];
+
+        this.$element
+          .val(value)
+          .trigger('changed' + EVENT_KEY, changedArguments);
+
+        if (this.$newElement.hasClass(classNames.SHOW)) {
+          if (this.multiple) {
+            this.setOptionStatus(true);
+          } else {
+            var liSelectedIndex = (element.options[element.selectedIndex] || {}).liIndex;
+
+            if (typeof liSelectedIndex === 'number') {
+              this.setSelected(this.selectedIndex, false);
+              this.setSelected(liSelectedIndex, true);
+            }
+          }
+        }
+
+        this.render();
+
+        changedArguments = null;
+
+        return this.$element;
+      } else {
+        return this.$element.val();
+      }
+    },
+
+    changeAll: function (status) {
+      if (!this.multiple) return;
+      if (typeof status === 'undefined') status = true;
+
+      var element = this.$element[0],
+          previousSelected = 0,
+          currentSelected = 0,
+          prevValue = getSelectValues(element);
+
+      element.classList.add('bs-select-hidden');
+
+      for (var i = 0, len = this.selectpicker.current.elements.length; i < len; i++) {
+        var liData = this.selectpicker.current.data[i],
+            option = liData.option;
+
+        if (option && !liData.disabled && liData.type !== 'divider') {
+          if (liData.selected) previousSelected++;
+          option.selected = status;
+          if (status) currentSelected++;
+        }
+      }
+
+      element.classList.remove('bs-select-hidden');
+
+      if (previousSelected === currentSelected) return;
+
+      this.setOptionStatus();
+
+      changedArguments = [null, null, prevValue];
+
+      this.$element
+        .triggerNative('change');
+    },
+
+    selectAll: function () {
+      return this.changeAll(true);
+    },
+
+    deselectAll: function () {
+      return this.changeAll(false);
+    },
+
+    toggle: function (e) {
+      e = e || window.event;
+
+      if (e) e.stopPropagation();
+
+      this.$button.trigger('click.bs.dropdown.data-api');
+    },
+
+    keydown: function (e) {
+      var $this = $(this),
+          isToggle = $this.hasClass('dropdown-toggle'),
+          $parent = isToggle ? $this.closest('.dropdown') : $this.closest(Selector.MENU),
+          that = $parent.data('this'),
+          $items = that.findLis(),
+          index,
+          isActive,
+          liActive,
+          activeLi,
+          offset,
+          updateScroll = false,
+          downOnTab = e.which === keyCodes.TAB && !isToggle && !that.options.selectOnTab,
+          isArrowKey = REGEXP_ARROW.test(e.which) || downOnTab,
+          scrollTop = that.$menuInner[0].scrollTop,
+          isVirtual = that.isVirtual(),
+          position0 = isVirtual === true ? that.selectpicker.view.position0 : 0;
+
+      isActive = that.$newElement.hasClass(classNames.SHOW);
+
+      if (
+        !isActive &&
+        (
+          isArrowKey ||
+          (e.which >= 48 && e.which <= 57) ||
+          (e.which >= 96 && e.which <= 105) ||
+          (e.which >= 65 && e.which <= 90)
+        )
+      ) {
+        that.$button.trigger('click.bs.dropdown.data-api');
+
+        if (that.options.liveSearch) {
+          that.$searchbox.trigger('focus');
+          return;
+        }
+      }
+
+      if (e.which === keyCodes.ESCAPE && isActive) {
+        e.preventDefault();
+        that.$button.trigger('click.bs.dropdown.data-api').trigger('focus');
+      }
+
+      if (isArrowKey) { // if up or down
+        if (!$items.length) return;
+
+        liActive = that.selectpicker.main.elements[that.activeIndex];
+        index = liActive ? Array.prototype.indexOf.call(liActive.parentElement.children, liActive) : -1;
+
+        if (index !== -1) {
+          that.defocusItem(liActive);
+        }
+
+        if (e.which === keyCodes.ARROW_UP) { // up
+          if (index !== -1) index--;
+          if (index + position0 < 0) index += $items.length;
+
+          if (!that.selectpicker.view.canHighlight[index + position0]) {
+            index = that.selectpicker.view.canHighlight.slice(0, index + position0).lastIndexOf(true) - position0;
+            if (index === -1) index = $items.length - 1;
+          }
+        } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down
+          index++;
+          if (index + position0 >= that.selectpicker.view.canHighlight.length) index = 0;
+
+          if (!that.selectpicker.view.canHighlight[index + position0]) {
+            index = index + 1 + that.selectpicker.view.canHighlight.slice(index + position0 + 1).indexOf(true);
+          }
+        }
+
+        e.preventDefault();
+
+        var liActiveIndex = position0 + index;
+
+        if (e.which === keyCodes.ARROW_UP) { // up
+          // scroll to bottom and highlight last option
+          if (position0 === 0 && index === $items.length - 1) {
+            that.$menuInner[0].scrollTop = that.$menuInner[0].scrollHeight;
+
+            liActiveIndex = that.selectpicker.current.elements.length - 1;
+          } else {
+            activeLi = that.selectpicker.current.data[liActiveIndex];
+            offset = activeLi.position - activeLi.height;
+
+            updateScroll = offset < scrollTop;
+          }
+        } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down
+          // scroll to top and highlight first option
+          if (index === 0) {
+            that.$menuInner[0].scrollTop = 0;
+
+            liActiveIndex = 0;
+          } else {
+            activeLi = that.selectpicker.current.data[liActiveIndex];
+            offset = activeLi.position - that.sizeInfo.menuInnerHeight;
+
+            updateScroll = offset > scrollTop;
+          }
+        }
+
+        liActive = that.selectpicker.current.elements[liActiveIndex];
+
+        that.activeIndex = that.selectpicker.current.data[liActiveIndex].index;
+
+        that.focusItem(liActive);
+
+        that.selectpicker.view.currentActive = liActive;
+
+        if (updateScroll) that.$menuInner[0].scrollTop = offset;
+
+        if (that.options.liveSearch) {
+          that.$searchbox.trigger('focus');
+        } else {
+          $this.trigger('focus');
+        }
+      } else if (
+        (!$this.is('input') && !REGEXP_TAB_OR_ESCAPE.test(e.which)) ||
+        (e.which === keyCodes.SPACE && that.selectpicker.keydown.keyHistory)
+      ) {
+        var searchMatch,
+            matches = [],
+            keyHistory;
+
+        e.preventDefault();
+
+        that.selectpicker.keydown.keyHistory += keyCodeMap[e.which];
+
+        if (that.selectpicker.keydown.resetKeyHistory.cancel) clearTimeout(that.selectpicker.keydown.resetKeyHistory.cancel);
+        that.selectpicker.keydown.resetKeyHistory.cancel = that.selectpicker.keydown.resetKeyHistory.start();
+
+        keyHistory = that.selectpicker.keydown.keyHistory;
+
+        // if all letters are the same, set keyHistory to just the first character when searching
+        if (/^(.)\1+$/.test(keyHistory)) {
+          keyHistory = keyHistory.charAt(0);
+        }
+
+        // find matches
+        for (var i = 0; i < that.selectpicker.current.data.length; i++) {
+          var li = that.selectpicker.current.data[i],
+              hasMatch;
+
+          hasMatch = stringSearch(li, keyHistory, 'startsWith', true);
+
+          if (hasMatch && that.selectpicker.view.canHighlight[i]) {
+            matches.push(li.index);
+          }
+        }
+
+        if (matches.length) {
+          var matchIndex = 0;
+
+          $items.removeClass('active').find('a').removeClass('active');
+
+          // either only one key has been pressed or they are all the same key
+          if (keyHistory.length === 1) {
+            matchIndex = matches.indexOf(that.activeIndex);
+
+            if (matchIndex === -1 || matchIndex === matches.length - 1) {
+              matchIndex = 0;
+            } else {
+              matchIndex++;
+            }
+          }
+
+          searchMatch = matches[matchIndex];
+
+          activeLi = that.selectpicker.main.data[searchMatch];
+
+          if (scrollTop - activeLi.position > 0) {
+            offset = activeLi.position - activeLi.height;
+            updateScroll = true;
+          } else {
+            offset = activeLi.position - that.sizeInfo.menuInnerHeight;
+            // if the option is already visible at the current scroll position, just keep it the same
+            updateScroll = activeLi.position > scrollTop + that.sizeInfo.menuInnerHeight;
+          }
+
+          liActive = that.selectpicker.main.elements[searchMatch];
+
+          that.activeIndex = matches[matchIndex];
+
+          that.focusItem(liActive);
+
+          if (liActive) liActive.firstChild.focus();
+
+          if (updateScroll) that.$menuInner[0].scrollTop = offset;
+
+          $this.trigger('focus');
+        }
+      }
+
+      // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu.
+      if (
+        isActive &&
+        (
+          (e.which === keyCodes.SPACE && !that.selectpicker.keydown.keyHistory) ||
+          e.which === keyCodes.ENTER ||
+          (e.which === keyCodes.TAB && that.options.selectOnTab)
+        )
+      ) {
+        if (e.which !== keyCodes.SPACE) e.preventDefault();
+
+        if (!that.options.liveSearch || e.which !== keyCodes.SPACE) {
+          that.$menuInner.find('.active a').trigger('click', true); // retain active class
+          $this.trigger('focus');
+
+          if (!that.options.liveSearch) {
+            // Prevent screen from scrolling if the user hits the spacebar
+            e.preventDefault();
+            // Fixes spacebar selection of dropdown items in FF & IE
+            $(document).data('spaceSelect', true);
+          }
+        }
+      }
+    },
+
+    mobile: function () {
+      this.$element[0].classList.add('mobile-device');
+    },
+
+    refresh: function () {
+      // update options if data attributes have been changed
+      var config = $.extend({}, this.options, this.$element.data());
+      this.options = config;
+
+      this.checkDisabled();
+      this.setStyle();
+      this.render();
+      this.createLi();
+      this.setWidth();
+
+      this.setSize(true);
+
+      this.$element.trigger('refreshed' + EVENT_KEY);
+    },
+
+    hide: function () {
+      this.$newElement.hide();
+    },
+
+    show: function () {
+      this.$newElement.show();
+    },
+
+    remove: function () {
+      this.$newElement.remove();
+      this.$element.remove();
+    },
+
+    destroy: function () {
+      this.$newElement.before(this.$element).remove();
+
+      if (this.$bsContainer) {
+        this.$bsContainer.remove();
+      } else {
+        this.$menu.remove();
+      }
+
+      this.$element
+        .off(EVENT_KEY)
+        .removeData('selectpicker')
+        .removeClass('bs-select-hidden selectpicker');
+
+      $(window).off(EVENT_KEY + '.' + this.selectId);
+    }
+  };
+
+  // SELECTPICKER PLUGIN DEFINITION
+  // ==============================
+  function Plugin (option) {
+    // get the args of the outer function..
+    var args = arguments;
+    // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
+    // to get lost/corrupted in android 2.3 and IE9 #715 #775
+    var _option = option;
+
+    [].shift.apply(args);
+
+    // if the version was not set successfully
+    if (!version.success) {
+      // try to retreive it again
+      try {
+        version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.');
+      } catch (err) {
+        // fall back to use BootstrapVersion if set
+        if (Selectpicker.BootstrapVersion) {
+          version.full = Selectpicker.BootstrapVersion.split(' ')[0].split('.');
+        } else {
+          version.full = [version.major, '0', '0'];
+
+          console.warn(
+            'There was an issue retrieving Bootstrap\'s version. ' +
+            'Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. ' +
+            'If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.',
+            err
+          );
+        }
+      }
+
+      version.major = version.full[0];
+      version.success = true;
+    }
+
+    if (version.major === '4') {
+      // some defaults need to be changed if using Bootstrap 4
+      // check to see if they have already been manually changed before forcing them to update
+      var toUpdate = [];
+
+      if (Selectpicker.DEFAULTS.style === classNames.BUTTONCLASS) toUpdate.push({ name: 'style', className: 'BUTTONCLASS' });
+      if (Selectpicker.DEFAULTS.iconBase === classNames.ICONBASE) toUpdate.push({ name: 'iconBase', className: 'ICONBASE' });
+      if (Selectpicker.DEFAULTS.tickIcon === classNames.TICKICON) toUpdate.push({ name: 'tickIcon', className: 'TICKICON' });
+
+      classNames.DIVIDER = 'dropdown-divider';
+      classNames.SHOW = 'show';
+      classNames.BUTTONCLASS = 'btn-light';
+      classNames.POPOVERHEADER = 'popover-header';
+      classNames.ICONBASE = '';
+      classNames.TICKICON = 'bs-ok-default';
+
+      for (var i = 0; i < toUpdate.length; i++) {
+        var option = toUpdate[i];
+        Selectpicker.DEFAULTS[option.name] = classNames[option.className];
+      }
+    }
+
+    var value;
+    var chain = this.each(function () {
+      var $this = $(this);
+      if ($this.is('select')) {
+        var data = $this.data('selectpicker'),
+            options = typeof _option == 'object' && _option;
+
+        if (!data) {
+          var dataAttributes = $this.data();
+
+          for (var dataAttr in dataAttributes) {
+            if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {
+              delete dataAttributes[dataAttr];
+            }
+          }
+
+          var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, dataAttributes, options);
+          config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), dataAttributes.template, options.template);
+          $this.data('selectpicker', (data = new Selectpicker(this, config)));
+        } else if (options) {
+          for (var i in options) {
+            if (options.hasOwnProperty(i)) {
+              data.options[i] = options[i];
+            }
+          }
+        }
+
+        if (typeof _option == 'string') {
+          if (data[_option] instanceof Function) {
+            value = data[_option].apply(data, args);
+          } else {
+            value = data.options[_option];
+          }
+        }
+      }
+    });
+
+    if (typeof value !== 'undefined') {
+      // noinspection JSUnusedAssignment
+      return value;
+    } else {
+      return chain;
+    }
+  }
+
+  var old = $.fn.selectpicker;
+  $.fn.selectpicker = Plugin;
+  $.fn.selectpicker.Constructor = Selectpicker;
+
+  // SELECTPICKER NO CONFLICT
+  // ========================
+  $.fn.selectpicker.noConflict = function () {
+    $.fn.selectpicker = old;
+    return this;
+  };
+
+  $(document)
+    .off('keydown.bs.dropdown.data-api')
+    .on('keydown' + EVENT_KEY, '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', Selectpicker.prototype.keydown)
+    .on('focusin.modal', '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', function (e) {
+      e.stopPropagation();
+    });
+
+  // SELECTPICKER DATA-API
+  // =====================
+  $(window).on('load' + EVENT_KEY + '.data-api', function () {
+    $('.selectpicker').each(function () {
+      var $selectpicker = $(this);
+      Plugin.call($selectpicker, $selectpicker.data());
+    })
+  });
+})(jQuery);
+
+
+}));
+//# sourceMappingURL=bootstrap-select.js.map
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css
new file mode 100644
index 0000000..dafcce3
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap-select v1.13.10 (https://developer.snapappointments.com/bootstrap-select)
+ *
+ * Copyright 2012-2019 SnapAppointments, LLC
+ * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
+ */.bootstrap-select>select.bs-select-hidden,select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\0;vertical-align:middle}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;text-align:right;white-space:nowrap;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.bootstrap-select>.dropdown-toggle:after{margin-top:-1px}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover{color:rgba(255,255,255,.5)}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none;z-index:0!important}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2!important}.bootstrap-select.is-invalid .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle,.was-validated .bootstrap-select .selectpicker:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select .selectpicker:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus,.bootstrap-select>select.mobile-device:focus+.dropdown-toggle{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none;height:auto}:not(.input-group)>.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{float:none;z-index:auto}.form-inline .bootstrap-select,.form-inline .bootstrap-select.form-control:not([class*=col-]){width:auto}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*=col-].dropdown-menu-right,.row .bootstrap-select[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select,.form-horizontal .bootstrap-select,.form-inline .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-lg .dropdown-toggle,.bootstrap-select.form-control-sm .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:0!important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0!important;padding:0!important}.bootstrap-select.bs-container .dropdown-menu{z-index:1060}.bootstrap-select .dropdown-toggle .filter-option{position:static;top:0;left:0;float:left;height:100%;width:100%;text-align:left;overflow:hidden;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.bs3.bootstrap-select .dropdown-toggle .filter-option{padding-right:inherit}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option{position:absolute;padding-top:inherit;padding-bottom:inherit;padding-left:inherit;float:none}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .filter-expand{width:0!important;float:left;opacity:0!important;overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:0!important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,.5)!important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before{content:'\00a0'}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:.5em;height:1em;border-style:solid;border-width:0 .26em .26em 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none}
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js
new file mode 100644
index 0000000..f9cb063
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap-select v1.13.10 (https://developer.snapappointments.com/bootstrap-select)
+ *
+ * Copyright 2012-2019 SnapAppointments, LLC
+ * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
+ */
+
+!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){!function(z){"use strict";var d=["sanitize","whiteList","sanitizeFn"],r=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],e={"*":["class","dir","id","lang","role","tabindex","style",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},l=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi,a=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;function v(e,t){var i=e.nodeName.toLowerCase();if(-1!==z.inArray(i,t))return-1===z.inArray(i,r)||Boolean(e.nodeValue.match(l)||e.nodeValue.match(a));for(var s=z(t).filter(function(e,t){return t instanceof RegExp}),n=0,o=s.length;n<o;n++)if(i.match(s[n]))return!0;return!1}function P(e,t,i){if(i&&"function"==typeof i)return i(e);for(var s=Object.keys(t),n=0,o=e.length;n<o;n++)for(var r=e[n].querySelectorAll("*"),l=0,a=r.length;l<a;l++){var c=r[l],d=c.nodeName.toLowerCase();if(-1!==s.indexOf(d))for(var h=[].slice.call(c.attributes),p=[].concat(t["*"]||[],t[d]||[]),u=0,f=h.length;u<f;u++){var m=h[u];v(m,p)||c.removeAttribute(m.nodeName)}else c.parentNode.removeChild(c)}}"classList"in document.createElement("_")||function(e){if("Element"in e){var t="classList",i="prototype",s=e.Element[i],n=Object,o=function(){var i=z(this);return{add:function(e){return e=Array.prototype.slice.call(arguments).join(" "),i.addClass(e)},remove:function(e){return e=Array.prototype.slice.call(arguments).join(" "),i.removeClass(e)},toggle:function(e,t){return i.toggleClass(e,t)},contains:function(e){return i.hasClass(e)}}};if(n.defineProperty){var r={get:o,enumerable:!0,configurable:!0};try{n.defineProperty(s,t,r)}catch(e){void 0!==e.number&&-2146823252!==e.number||(r.enumerable=!1,n.defineProperty(s,t,r))}}else n[i].__defineGetter__&&s.__defineGetter__(t,o)}}(window);var t,c,i,s=document.createElement("_");if(s.classList.add("c1","c2"),!s.classList.contains("c2")){var n=DOMTokenList.prototype.add,o=DOMTokenList.prototype.remove;DOMTokenList.prototype.add=function(){Array.prototype.forEach.call(arguments,n.bind(this))},DOMTokenList.prototype.remove=function(){Array.prototype.forEach.call(arguments,o.bind(this))}}if(s.classList.toggle("c3",!1),s.classList.contains("c3")){var h=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:h.call(this,e)}}function O(e,t){for(var i,s=[],n=t||e.selectedOptions,o=0,r=n.length;o<r;o++)(i=n[o]).disabled||"OPTGROUP"===i.parentNode.tagName&&i.parentNode.disabled||s.push(i.value||i.text);return e.multiple?s:s.length?s[0]:null}s=null,String.prototype.startsWith||(t=function(){try{var e={},t=Object.defineProperty,i=t(e,e,e)&&t}catch(e){}return i}(),c={}.toString,i=function(e){if(null==this)throw new TypeError;var t=String(this);if(e&&"[object RegExp]"==c.call(e))throw new TypeError;var i=t.length,s=String(e),n=s.length,o=1<arguments.length?arguments[1]:void 0,r=o?Number(o):0;r!=r&&(r=0);var l=Math.min(Math.max(r,0),i);if(i<n+l)return!1;for(var a=-1;++a<n;)if(t.charCodeAt(l+a)!=s.charCodeAt(a))return!1;return!0},t?t(String.prototype,"startsWith",{value:i,configurable:!0,writable:!0}):String.prototype.startsWith=i),Object.keys||(Object.keys=function(e,t,i){for(t in i=[],e)i.hasOwnProperty.call(e,t)&&i.push(t);return i}),HTMLSelectElement&&!HTMLSelectElement.prototype.hasOwnProperty("selectedOptions")&&Object.defineProperty(HTMLSelectElement.prototype,"selectedOptions",{get:function(){return this.querySelectorAll(":checked")}});var p={useDefault:!1,_set:z.valHooks.select.set};z.valHooks.select.set=function(e,t){return t&&!p.useDefault&&z(e).data("selected",!0),p._set.apply(this,arguments)};var T=null,u=function(){try{return new Event("change"),!0}catch(e){return!1}}();function k(e,t,i,s){for(var n=["display","subtext","tokens"],o=!1,r=0;r<n.length;r++){var l=n[r],a=e[l];if(a&&(a=a.toString(),"display"===l&&(a=a.replace(/<[^>]+>/g,"")),s&&(a=w(a)),a=a.toUpperCase(),o="contains"===i?0<=a.indexOf(t):a.startsWith(t)))break}return o}function A(e){return parseInt(e,10)||0}z.fn.triggerNative=function(e){var t,i=this[0];i.dispatchEvent?(u?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),i.dispatchEvent(t)):i.fireEvent?((t=document.createEventObject()).eventType=e,i.fireEvent("on"+e,t)):this.trigger(e)};var f={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},m=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,g=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function b(e){return f[e]}function w(e){return(e=e.toString())&&e.replace(m,b).replace(g,"")}var I,x,$,y,S,E=(I={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},x=function(e){return I[e]},$="(?:"+Object.keys(I).join("|")+")",y=RegExp($),S=RegExp($,"g"),function(e){return e=null==e?"":""+e,y.test(e)?e.replace(S,x):e}),C={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},L=27,N=13,D=32,H=9,B=38,W=40,M={success:!1,major:"3"};try{M.full=(z.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),M.major=M.full[0],M.success=!0}catch(e){}var R=0,U=".bs.select",j={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},V={MENU:"."+j.MENU},F={span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode("\xa0"),fragment:document.createDocumentFragment()};F.a.setAttribute("role","option"),F.subtext.className="text-muted",F.text=F.span.cloneNode(!1),F.text.className="text",F.checkMark=F.span.cloneNode(!1);var _=new RegExp(B+"|"+W),G=new RegExp("^"+H+"$|"+L),q=function(e,t,i){var s=F.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?s.appendChild(e):s.innerHTML=e),void 0!==t&&""!==t&&(s.className=t),null!=i&&s.classList.add("optgroup-"+i),s},K=function(e,t,i){var s=F.a.cloneNode(!0);return e&&(11===e.nodeType?s.appendChild(e):s.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&(s.className=t),"4"===M.major&&s.classList.add("dropdown-item"),i&&s.setAttribute("style",i),s},Y=function(e,t){var i,s,n=F.text.cloneNode(!1);if(e.content)n.innerHTML=e.content;else{if(n.textContent=e.text,e.icon){var o=F.whitespace.cloneNode(!1);(s=(!0===t?F.i:F.span).cloneNode(!1)).className=e.iconBase+" "+e.icon,F.fragment.appendChild(s),F.fragment.appendChild(o)}e.subtext&&((i=F.subtext.cloneNode(!1)).textContent=e.subtext,n.appendChild(i))}if(!0===t)for(;0<n.childNodes.length;)F.fragment.appendChild(n.childNodes[0]);else F.fragment.appendChild(n);return F.fragment},Z=function(e){var t,i,s=F.text.cloneNode(!1);if(s.innerHTML=e.label,e.icon){var n=F.whitespace.cloneNode(!1);(i=F.span.cloneNode(!1)).className=e.iconBase+" "+e.icon,F.fragment.appendChild(i),F.fragment.appendChild(n)}return e.subtext&&((t=F.subtext.cloneNode(!1)).textContent=e.subtext,s.appendChild(t)),F.fragment.appendChild(s),F.fragment},J=function(e,t){var i=this;p.useDefault||(z.valHooks.select.set=p._set,p.useDefault=!0),this.$element=z(e),this.$newElement=null,this.$button=null,this.$menu=null,this.options=t,this.selectpicker={main:{},search:{},current:{},view:{},keydown:{keyHistory:"",resetKeyHistory:{start:function(){return setTimeout(function(){i.selectpicker.keydown.keyHistory=""},800)}}}},null===this.options.title&&(this.options.title=this.$element.attr("title"));var s=this.options.windowPadding;"number"==typeof s&&(this.options.windowPadding=[s,s,s,s]),this.val=J.prototype.val,this.render=J.prototype.render,this.refresh=J.prototype.refresh,this.setStyle=J.prototype.setStyle,this.selectAll=J.prototype.selectAll,this.deselectAll=J.prototype.deselectAll,this.destroy=J.prototype.destroy,this.remove=J.prototype.remove,this.show=J.prototype.show,this.hide=J.prototype.hide,this.init()};function Q(e){var l,a=arguments,c=e;if([].shift.apply(a),!M.success){try{M.full=(z.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split(".")}catch(e){J.BootstrapVersion?M.full=J.BootstrapVersion.split(" ")[0].split("."):(M.full=[M.major,"0","0"],console.warn("There was an issue retrieving Bootstrap's version. Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.",e))}M.major=M.full[0],M.success=!0}if("4"===M.major){var t=[];J.DEFAULTS.style===j.BUTTONCLASS&&t.push({name:"style",className:"BUTTONCLASS"}),J.DEFAULTS.iconBase===j.ICONBASE&&t.push({name:"iconBase",className:"ICONBASE"}),J.DEFAULTS.tickIcon===j.TICKICON&&t.push({name:"tickIcon",className:"TICKICON"}),j.DIVIDER="dropdown-divider",j.SHOW="show",j.BUTTONCLASS="btn-light",j.POPOVERHEADER="popover-header",j.ICONBASE="",j.TICKICON="bs-ok-default";for(var i=0;i<t.length;i++){e=t[i];J.DEFAULTS[e.name]=j[e.className]}}var s=this.each(function(){var e=z(this);if(e.is("select")){var t=e.data("selectpicker"),i="object"==typeof c&&c;if(t){if(i)for(var s in i)i.hasOwnProperty(s)&&(t.options[s]=i[s])}else{var n=e.data();for(var o in n)n.hasOwnProperty(o)&&-1!==z.inArray(o,d)&&delete n[o];var r=z.extend({},J.DEFAULTS,z.fn.selectpicker.defaults||{},n,i);r.template=z.extend({},J.DEFAULTS.template,z.fn.selectpicker.defaults?z.fn.selectpicker.defaults.template:{},n.template,i.template),e.data("selectpicker",t=new J(this,r))}"string"==typeof c&&(l=t[c]instanceof Function?t[c].apply(t,a):t.options[c])}});return void 0!==l?l:s}J.VERSION="1.13.10",J.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(e,t){return 1==e?"{0} item selected":"{0} items selected"},maxOptionsText:function(e,t){return[1==e?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==t?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:j.BUTTONCLASS,size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:j.ICONBASE,tickIcon:j.TICKICON,showTick:!1,template:{caret:'<span class="caret"></span>'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:e},J.prototype={constructor:J,init:function(){var i=this,e=this.$element.attr("id");R++,this.selectId="bs-select-"+R,this.$element[0].classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$element[0].classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.$element.after(this.$newElement).prependTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(V.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element[0].classList.remove("bs-select-hidden"),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(j.MENURIGHT),void 0!==e&&this.$button.attr("data-id",e),this.checkDisabled(),this.clickListener(),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.render(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+U,function(){if(i.isVirtual()){var e=i.$menuInner[0],t=e.firstChild.cloneNode(!1);e.replaceChild(t,e.firstChild),e.scrollTop=0}}),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(e){i.$element.trigger("hide"+U,e)},"hidden.bs.dropdown":function(e){i.$element.trigger("hidden"+U,e)},"show.bs.dropdown":function(e){i.$element.trigger("show"+U,e)},"shown.bs.dropdown":function(e){i.$element.trigger("shown"+U,e)}}),i.$element[0].hasAttribute("required")&&this.$element.on("invalid"+U,function(){i.$button[0].classList.add("bs-invalid"),i.$element.on("shown"+U+".invalid",function(){i.$element.val(i.$element.val()).off("shown"+U+".invalid")}).on("rendered"+U,function(){this.validity.valid&&i.$button[0].classList.remove("bs-invalid"),i.$element.off("rendered"+U)}),i.$button.on("blur"+U,function(){i.$element.trigger("focus").trigger("blur"),i.$button.off("blur"+U)})}),setTimeout(function(){i.createLi(),i.$element.trigger("loaded"+U)})},createDropdown:function(){var e=this.multiple||this.options.showTick?" show-tick":"",t=this.multiple?' aria-multiselectable="true"':"",i="",s=this.autofocus?" autofocus":"";M.major<4&&this.$element.parent().hasClass("input-group")&&(i=" input-group-btn");var n,o="",r="",l="",a="";return this.options.header&&(o='<div class="'+j.POPOVERHEADER+'"><button type="button" class="close" aria-hidden="true">&times;</button>'+this.options.header+"</div>"),this.options.liveSearch&&(r='<div class="bs-searchbox"><input type="text" class="form-control" autocomplete="off"'+(null===this.options.liveSearchPlaceholder?"":' placeholder="'+E(this.options.liveSearchPlaceholder)+'"')+' role="combobox" aria-label="Search" aria-controls="'+this.selectId+'" aria-autocomplete="list"></div>'),this.multiple&&this.options.actionsBox&&(l='<div class="bs-actionsbox"><div class="btn-group btn-group-sm btn-block"><button type="button" class="actions-btn bs-select-all btn '+j.BUTTONCLASS+'">'+this.options.selectAllText+'</button><button type="button" class="actions-btn bs-deselect-all btn '+j.BUTTONCLASS+'">'+this.options.deselectAllText+"</button></div></div>"),this.multiple&&this.options.doneButton&&(a='<div class="bs-donebutton"><div class="btn-group btn-block"><button type="button" class="btn btn-sm '+j.BUTTONCLASS+'">'+this.options.doneButtonText+"</button></div></div>"),n='<div class="dropdown bootstrap-select'+e+i+'"><button type="button" class="'+this.options.styleBase+' dropdown-toggle" '+("static"===this.options.display?'data-display="static"':"")+'data-toggle="dropdown"'+s+' role="combobox" aria-owns="'+this.selectId+'" aria-haspopup="listbox" aria-expanded="false"><div class="filter-option"><div class="filter-option-inner"><div class="filter-option-inner-inner"></div></div> </div>'+("4"===M.major?"":'<span class="bs-caret">'+this.options.template.caret+"</span>")+'</button><div class="'+j.MENU+" "+("4"===M.major?"":j.SHOW)+'">'+o+r+l+'<div class="inner '+j.SHOW+'" role="listbox" id="'+this.selectId+'" tabindex="-1" '+t+'><ul class="'+j.MENU+" inner "+("4"===M.major?j.SHOW:"")+'" role="presentation"></ul></div>'+a+"</div></div>",z(n)},setPositionData:function(){this.selectpicker.view.canHighlight=[];for(var e=this.selectpicker.view.size=0;e<this.selectpicker.current.data.length;e++){var t=this.selectpicker.current.data[e],i=!0;"divider"===t.type?(i=!1,t.height=this.sizeInfo.dividerHeight):"optgroup-label"===t.type?(i=!1,t.height=this.sizeInfo.dropdownHeaderHeight):t.height=this.sizeInfo.liHeight,t.disabled&&(i=!1),this.selectpicker.view.canHighlight.push(i),i&&(this.selectpicker.view.size++,t.posinset=this.selectpicker.view.size),t.position=(0===e?0:this.selectpicker.current.data[e-1].position)+t.height}},isVirtual:function(){return!1!==this.options.virtualScroll&&this.selectpicker.main.elements.length>=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(A,e,t){var L,N,D=this,i=0,H=[];if(this.selectpicker.current=A?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),e)if(t)i=this.$menuInner[0].scrollTop;else if(!D.multiple){var s=D.$element[0],n=(s.options[s.selectedIndex]||{}).liIndex;if("number"==typeof n&&!1!==D.options.size){var o=D.selectpicker.main.data[n],r=o&&o.position;r&&(i=r-(D.sizeInfo.menuInnerHeight+D.sizeInfo.liHeight)/2)}}function l(e,t){var i,s,n,o,r,l,a,c,d,h,p=D.selectpicker.current.elements.length,u=[],f=!0,m=D.isVirtual();D.selectpicker.view.scrollTop=e,!0===m&&D.sizeInfo.hasScrollBar&&D.$menu[0].offsetWidth>D.sizeInfo.totalMenuWidth&&(D.sizeInfo.menuWidth=D.$menu[0].offsetWidth,D.sizeInfo.totalMenuWidth=D.sizeInfo.menuWidth+D.sizeInfo.scrollBarWidth,D.$menu.css("min-width",D.sizeInfo.menuWidth)),i=Math.ceil(D.sizeInfo.menuInnerHeight/D.sizeInfo.liHeight*1.5),s=Math.round(p/i)||1;for(var v=0;v<s;v++){var g=(v+1)*i;if(v===s-1&&(g=p),u[v]=[v*i+(v?1:0),g],!p)break;void 0===r&&e<=D.selectpicker.current.data[g-1].position-D.sizeInfo.menuInnerHeight&&(r=v)}if(void 0===r&&(r=0),l=[D.selectpicker.view.position0,D.selectpicker.view.position1],n=Math.max(0,r-1),o=Math.min(s-1,r+1),D.selectpicker.view.position0=!1===m?0:Math.max(0,u[n][0])||0,D.selectpicker.view.position1=!1===m?p:Math.min(p,u[o][1])||0,a=l[0]!==D.selectpicker.view.position0||l[1]!==D.selectpicker.view.position1,void 0!==D.activeIndex&&(N=D.selectpicker.main.elements[D.prevActiveIndex],H=D.selectpicker.main.elements[D.activeIndex],L=D.selectpicker.main.elements[D.selectedIndex],t&&(D.activeIndex!==D.selectedIndex&&D.defocusItem(H),D.activeIndex=void 0),D.activeIndex&&D.activeIndex!==D.selectedIndex&&D.defocusItem(L)),void 0!==D.prevActiveIndex&&D.prevActiveIndex!==D.activeIndex&&D.prevActiveIndex!==D.selectedIndex&&D.defocusItem(N),(t||a)&&(c=D.selectpicker.view.visibleElements?D.selectpicker.view.visibleElements.slice():[],D.selectpicker.view.visibleElements=!1===m?D.selectpicker.current.elements:D.selectpicker.current.elements.slice(D.selectpicker.view.position0,D.selectpicker.view.position1),D.setOptionStatus(),(A||!1===m&&t)&&(d=c,h=D.selectpicker.view.visibleElements,f=!(d.length===h.length&&d.every(function(e,t){return e===h[t]}))),(t||!0===m)&&f)){var b,w,I=D.$menuInner[0],x=document.createDocumentFragment(),k=I.firstChild.cloneNode(!1),$=D.selectpicker.view.visibleElements,y=[];I.replaceChild(k,I.firstChild);v=0;for(var S=$.length;v<S;v++){var E,C,O=$[v];D.options.sanitize&&(E=O.lastChild)&&(C=D.selectpicker.current.data[v+D.selectpicker.view.position0])&&C.content&&!C.sanitized&&(y.push(E),C.sanitized=!0),x.appendChild(O)}D.options.sanitize&&y.length&&P(y,D.options.whiteList,D.options.sanitizeFn),I.firstChild.style.marginBottom=!0===m?(b=0===D.selectpicker.view.position0?0:D.selectpicker.current.data[D.selectpicker.view.position0-1].position,w=D.selectpicker.view.position1>p-1?0:D.selectpicker.current.data[p-1].position-D.selectpicker.current.data[D.selectpicker.view.position1-1].position,I.firstChild.style.marginTop=b+"px",w+"px"):I.firstChild.style.marginTop=0,I.firstChild.appendChild(x)}if(D.prevActiveIndex=D.activeIndex,D.options.liveSearch){if(A&&t){var z,T=0;D.selectpicker.view.canHighlight[T]||(T=1+D.selectpicker.view.canHighlight.slice(1).indexOf(!0)),z=D.selectpicker.view.visibleElements[T],D.defocusItem(D.selectpicker.view.currentActive),D.activeIndex=(D.selectpicker.current.data[T]||{}).index,D.focusItem(z)}}else D.$menuInner.trigger("focus")}l(i,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",function(e,t){D.noScroll||l(this.scrollTop,t),D.noScroll=!1}),z(window).off("resize"+U+"."+this.selectId+".createView").on("resize"+U+"."+this.selectId+".createView",function(){D.$newElement.hasClass(j.SHOW)&&l(D.$menuInner[0].scrollTop)})},focusItem:function(e,t,i){if(e){t=t||this.selectpicker.main.data[this.activeIndex];var s=e.firstChild;s&&(s.setAttribute("aria-setsize",this.selectpicker.view.size),s.setAttribute("aria-posinset",t.posinset),!0!==i&&(this.focusedParent.setAttribute("aria-activedescendant",s.id),e.classList.add("active"),s.classList.add("active")))}},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),e=!0;var t=this.$element[0],i=!1,s=!this.selectpicker.view.titleOption.parentNode;if(s)this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",i=void 0===z(t.options[t.selectedIndex]).attr("selected")&&void 0===this.$element.data("selected");(s||0!==this.selectpicker.view.titleOption.index)&&t.insertBefore(this.selectpicker.view.titleOption,t.firstChild),i&&(t.selectedIndex=0)}return e},createLi:function(){var c=this,f=this.options.iconBase,m=':not([hidden]):not([data-hidden="true"])',v=[],g=[],d=0,b=0,e=this.setPlaceholder()?1:0;this.options.hideDisabled&&(m+=":not(:disabled)"),!c.options.showTick&&!c.multiple||F.checkMark.parentNode||(F.checkMark.className=f+" "+c.options.tickIcon+" check-mark",F.a.appendChild(F.checkMark));var t=this.$element[0].querySelectorAll("select > *"+m);function w(e){var t=g[g.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",v.push(q(!1,j.DIVIDER,e.optID?e.optID+"div":void 0)),g.push(e))}function I(e,t){if((t=t||{}).divider="true"===e.getAttribute("data-divider"),t.divider)w({optID:t.optID});else{var i=g.length,s=e.style.cssText,n=s?E(s):"",o=(e.className||"")+(t.optgroupClass||"");t.optID&&(o="opt "+o),t.text=e.textContent,t.content=e.getAttribute("data-content"),t.tokens=e.getAttribute("data-tokens"),t.subtext=e.getAttribute("data-subtext"),t.icon=e.getAttribute("data-icon"),t.iconBase=f;var r=Y(t),l=q(K(r,o,n),"",t.optID);l.firstChild&&(l.firstChild.id=c.selectId+"-"+i),v.push(l),e.liIndex=i,t.display=t.content||t.text,t.type="option",t.index=i,t.option=e,t.disabled=t.disabled||e.disabled,g.push(t);var a=0;t.display&&(a+=t.display.length),t.subtext&&(a+=t.subtext.length),t.icon&&(a+=1),d<a&&(d=a,c.selectpicker.view.widestOption=v[v.length-1])}}function i(e,t){var i=t[e],s=t[e-1],n=t[e+1],o=i.querySelectorAll("option"+m);if(o.length){var r,l,a={label:E(i.label),subtext:i.getAttribute("data-subtext"),icon:i.getAttribute("data-icon"),iconBase:f},c=" "+(i.className||"");b++,s&&w({optID:b});var d=Z(a);v.push(q(d,"dropdown-header"+c,b)),g.push({display:a.label,subtext:a.subtext,type:"optgroup-label",optID:b});for(var h=0,p=o.length;h<p;h++){var u=o[h];0===h&&(l=(r=g.length-1)+p),I(u,{headerIndex:r,lastIndex:l,optID:b,optgroupClass:c,disabled:i.disabled})}n&&w({optID:b})}}for(var s=t.length;e<s;e++){var n=t[e];"OPTGROUP"!==n.tagName?I(n,{}):i(e,t)}this.selectpicker.main.elements=v,this.selectpicker.main.data=g,this.selectpicker.current=this.selectpicker.main},findLis:function(){return this.$menuInner.find(".inner > li")},render:function(){this.setPlaceholder();var e,t,i=this,s=this.$element[0],n=function(e,t){var i,s=e.selectedOptions,n=[];if(t){for(var o=0,r=s.length;o<r;o++)(i=s[o]).disabled||"OPTGROUP"===i.parentNode.tagName&&i.parentNode.disabled||n.push(i);return n}return s}(s,this.options.hideDisabled),o=n.length,r=this.$button[0],l=r.querySelector(".filter-option-inner-inner"),a=document.createTextNode(this.options.multipleSeparator),c=F.fragment.cloneNode(!1),d=!1;if(r.classList.toggle("bs-placeholder",i.multiple?!o:!O(s,n)),this.tabIndex(),"static"===this.options.selectedTextFormat)c=Y({text:this.options.title},!0);else if((e=this.multiple&&-1!==this.options.selectedTextFormat.indexOf("count")&&1<o)&&(e=1<(t=this.options.selectedTextFormat.split(">")).length&&o>t[1]||1===t.length&&2<=o),!1===e){for(var h=0;h<o&&h<50;h++){var p=n[h],u={},f={content:p.getAttribute("data-content"),subtext:p.getAttribute("data-subtext"),icon:p.getAttribute("data-icon")};this.multiple&&0<h&&c.appendChild(a.cloneNode(!1)),p.title?u.text=p.title:f.content&&i.options.showContent?(u.content=f.content.toString(),d=!0):(i.options.showIcon&&(u.icon=f.icon,u.iconBase=this.options.iconBase),i.options.showSubtext&&!i.multiple&&f.subtext&&(u.subtext=" "+f.subtext),u.text=p.textContent.trim()),c.appendChild(Y(u,!0))}49<o&&c.appendChild(document.createTextNode("..."))}else{var m=':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])';this.options.hideDisabled&&(m+=":not(:disabled)");var v=this.$element[0].querySelectorAll("select > option"+m+", optgroup"+m+" option"+m).length,g="function"==typeof this.options.countSelectedText?this.options.countSelectedText(o,v):this.options.countSelectedText;c=Y({text:g.replace("{0}",o.toString()).replace("{1}",v.toString())},!0)}if(null==this.options.title&&(this.options.title=this.$element.attr("title")),c.childNodes.length||(c=Y({text:void 0!==this.options.title?this.options.title:this.options.noneSelectedText},!0)),r.title=c.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&d&&P([c],i.options.whiteList,i.options.sanitizeFn),l.innerHTML="",l.appendChild(c),M.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")){var b=r.querySelector(".filter-expand"),w=l.cloneNode(!0);w.className="filter-expand",b?r.replaceChild(w,b):r.appendChild(w)}this.$element.trigger("rendered"+U)},setStyle:function(e,t){var i,s=this.$button[0],n=this.$newElement[0],o=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),M.major<4&&(n.classList.add("bs3"),n.parentNode.classList.contains("input-group")&&(n.previousElementSibling||n.nextElementSibling)&&(n.previousElementSibling||n.nextElementSibling).classList.contains("input-group-addon")&&n.classList.add("bs3-has-addon")),i=e?e.trim():o,"add"==t?i&&s.classList.add.apply(s.classList,i.split(" ")):"remove"==t?i&&s.classList.remove.apply(s.classList,i.split(" ")):(o&&s.classList.remove.apply(s.classList,o.split(" ")),i&&s.classList.add.apply(s.classList,i.split(" ")))},liHeight:function(e){if(e||!1!==this.options.size&&!this.sizeInfo){this.sizeInfo||(this.sizeInfo={});var t=document.createElement("div"),i=document.createElement("div"),s=document.createElement("div"),n=document.createElement("ul"),o=document.createElement("li"),r=document.createElement("li"),l=document.createElement("li"),a=document.createElement("a"),c=document.createElement("span"),d=this.options.header&&0<this.$menu.find("."+j.POPOVERHEADER).length?this.$menu.find("."+j.POPOVERHEADER)[0].cloneNode(!0):null,h=this.options.liveSearch?document.createElement("div"):null,p=this.options.actionsBox&&this.multiple&&0<this.$menu.find(".bs-actionsbox").length?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,u=this.options.doneButton&&this.multiple&&0<this.$menu.find(".bs-donebutton").length?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null,f=this.$element.find("option")[0];if(this.sizeInfo.selectWidth=this.$newElement[0].offsetWidth,c.className="text",a.className="dropdown-item "+(f?f.className:""),t.className=this.$menu[0].parentNode.className+" "+j.SHOW,t.style.width=this.sizeInfo.selectWidth+"px","auto"===this.options.width&&(i.style.minWidth=0),i.className=j.MENU+" "+j.SHOW,s.className="inner "+j.SHOW,n.className=j.MENU+" inner "+("4"===M.major?j.SHOW:""),o.className=j.DIVIDER,r.className="dropdown-header",c.appendChild(document.createTextNode("\u200b")),a.appendChild(c),l.appendChild(a),r.appendChild(c.cloneNode(!0)),this.selectpicker.view.widestOption&&n.appendChild(this.selectpicker.view.widestOption.cloneNode(!0)),n.appendChild(l),n.appendChild(o),n.appendChild(r),d&&i.appendChild(d),h){var m=document.createElement("input");h.className="bs-searchbox",m.className="form-control",h.appendChild(m),i.appendChild(h)}p&&i.appendChild(p),s.appendChild(n),i.appendChild(s),u&&i.appendChild(u),t.appendChild(i),document.body.appendChild(t);var v,g=l.offsetHeight,b=r?r.offsetHeight:0,w=d?d.offsetHeight:0,I=h?h.offsetHeight:0,x=p?p.offsetHeight:0,k=u?u.offsetHeight:0,$=z(o).outerHeight(!0),y=!!window.getComputedStyle&&window.getComputedStyle(i),S=i.offsetWidth,E=y?null:z(i),C={vert:A(y?y.paddingTop:E.css("paddingTop"))+A(y?y.paddingBottom:E.css("paddingBottom"))+A(y?y.borderTopWidth:E.css("borderTopWidth"))+A(y?y.borderBottomWidth:E.css("borderBottomWidth")),horiz:A(y?y.paddingLeft:E.css("paddingLeft"))+A(y?y.paddingRight:E.css("paddingRight"))+A(y?y.borderLeftWidth:E.css("borderLeftWidth"))+A(y?y.borderRightWidth:E.css("borderRightWidth"))},O={vert:C.vert+A(y?y.marginTop:E.css("marginTop"))+A(y?y.marginBottom:E.css("marginBottom"))+2,horiz:C.horiz+A(y?y.marginLeft:E.css("marginLeft"))+A(y?y.marginRight:E.css("marginRight"))+2};s.style.overflowY="scroll",v=i.offsetWidth-S,document.body.removeChild(t),this.sizeInfo.liHeight=g,this.sizeInfo.dropdownHeaderHeight=b,this.sizeInfo.headerHeight=w,this.sizeInfo.searchHeight=I,this.sizeInfo.actionsHeight=x,this.sizeInfo.doneButtonHeight=k,this.sizeInfo.dividerHeight=$,this.sizeInfo.menuPadding=C,this.sizeInfo.menuExtras=O,this.sizeInfo.menuWidth=S,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth,this.sizeInfo.scrollBarWidth=v,this.sizeInfo.selectHeight=this.$newElement[0].offsetHeight,this.setPositionData()}},getSelectPosition:function(){var e,t=z(window),i=this.$newElement.offset(),s=z(this.options.container);this.options.container&&s.length&&!s.is("body")?((e=s.offset()).top+=parseInt(s.css("borderTopWidth")),e.left+=parseInt(s.css("borderLeftWidth"))):e={top:0,left:0};var n=this.options.windowPadding;this.sizeInfo.selectOffsetTop=i.top-e.top-t.scrollTop(),this.sizeInfo.selectOffsetBot=t.height()-this.sizeInfo.selectOffsetTop-this.sizeInfo.selectHeight-e.top-n[2],this.sizeInfo.selectOffsetLeft=i.left-e.left-t.scrollLeft(),this.sizeInfo.selectOffsetRight=t.width()-this.sizeInfo.selectOffsetLeft-this.sizeInfo.selectWidth-e.left-n[1],this.sizeInfo.selectOffsetTop-=n[0],this.sizeInfo.selectOffsetLeft-=n[3]},setMenuSize:function(e){this.getSelectPosition();var t,i,s,n,o,r,l,a=this.sizeInfo.selectWidth,c=this.sizeInfo.liHeight,d=this.sizeInfo.headerHeight,h=this.sizeInfo.searchHeight,p=this.sizeInfo.actionsHeight,u=this.sizeInfo.doneButtonHeight,f=this.sizeInfo.dividerHeight,m=this.sizeInfo.menuPadding,v=0;if(this.options.dropupAuto&&(l=c*this.selectpicker.current.elements.length+m.vert,this.$newElement.toggleClass(j.DROPUP,this.sizeInfo.selectOffsetTop-this.sizeInfo.selectOffsetBot>this.sizeInfo.menuExtras.vert&&l+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot)),"auto"===this.options.size)n=3<this.selectpicker.current.elements.length?3*this.sizeInfo.liHeight+this.sizeInfo.menuExtras.vert-2:0,i=this.sizeInfo.selectOffsetBot-this.sizeInfo.menuExtras.vert,s=n+d+h+p+u,r=Math.max(n-m.vert,0),this.$newElement.hasClass(j.DROPUP)&&(i=this.sizeInfo.selectOffsetTop-this.sizeInfo.menuExtras.vert),t=(o=i)-d-h-p-u-m.vert;else if(this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size){for(var g=0;g<this.options.size;g++)"divider"===this.selectpicker.current.data[g].type&&v++;t=(i=c*this.options.size+v*f+m.vert)-m.vert,o=i+d+h+p+u,s=r=""}"auto"===this.options.dropdownAlignRight&&this.$menu.toggleClass(j.MENURIGHT,this.sizeInfo.selectOffsetLeft>this.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRight<this.sizeInfo.totalMenuWidth-a),this.$menu.css({"max-height":o+"px",overflow:"hidden","min-height":s+"px"}),this.$menuInner.css({"max-height":t+"px","overflow-y":"auto","min-height":r+"px"}),this.sizeInfo.menuInnerHeight=Math.max(t,1),this.selectpicker.current.data.length&&this.selectpicker.current.data[this.selectpicker.current.data.length-1].position>this.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth,this.$menu.css("min-width",this.sizeInfo.totalMenuWidth)),this.dropdown&&this.dropdown._popper&&this.dropdown._popper.update()},setSize:function(e){if(this.liHeight(e),this.options.header&&this.$menu.css("padding-top",0),!1!==this.options.size){var t=this,i=z(window);this.setMenuSize(),this.options.liveSearch&&this.$searchbox.off("input.setMenuSize propertychange.setMenuSize").on("input.setMenuSize propertychange.setMenuSize",function(){return t.setMenuSize()}),"auto"===this.options.size?i.off("resize"+U+"."+this.selectId+".setMenuSize scroll"+U+"."+this.selectId+".setMenuSize").on("resize"+U+"."+this.selectId+".setMenuSize scroll"+U+"."+this.selectId+".setMenuSize",function(){return t.setMenuSize()}):this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size&&i.off("resize"+U+"."+this.selectId+".setMenuSize scroll"+U+"."+this.selectId+".setMenuSize"),t.createView(!1,!0,e)}},setWidth:function(){var i=this;"auto"===this.options.width?requestAnimationFrame(function(){i.$menu.css("min-width","0"),i.$element.on("loaded"+U,function(){i.liHeight(),i.setMenuSize();var e=i.$newElement.clone().appendTo("body"),t=e.css("width","auto").children("button").outerWidth();e.remove(),i.sizeInfo.selectWidth=Math.max(i.sizeInfo.totalMenuWidth,t),i.$newElement.css("width",i.sizeInfo.selectWidth+"px")})}):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=z('<div class="bs-container" />');var s,n,o,r=this,l=z(this.options.container),e=function(e){var t={},i=r.options.display||!!z.fn.dropdown.Constructor.Default&&z.fn.dropdown.Constructor.Default.display;r.$bsContainer.addClass(e.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(j.DROPUP,e.hasClass(j.DROPUP)),s=e.offset(),l.is("body")?n={top:0,left:0}:((n=l.offset()).top+=parseInt(l.css("borderTopWidth"))-l.scrollTop(),n.left+=parseInt(l.css("borderLeftWidth"))-l.scrollLeft()),o=e.hasClass(j.DROPUP)?0:e[0].offsetHeight,(M.major<4||"static"===i)&&(t.top=s.top-n.top+o,t.left=s.left-n.left),t.width=e[0].offsetWidth,r.$bsContainer.css(t)};this.$button.on("click.bs.dropdown.data-api",function(){r.isDisabled()||(e(r.$newElement),r.$bsContainer.appendTo(r.options.container).toggleClass(j.SHOW,!r.$button.hasClass(j.SHOW)).append(r.$menu))}),z(window).off("resize"+U+"."+this.selectId+" scroll"+U+"."+this.selectId).on("resize"+U+"."+this.selectId+" scroll"+U+"."+this.selectId,function(){r.$newElement.hasClass(j.SHOW)&&e(r.$newElement)}),this.$element.on("hide"+U,function(){r.$menu.data("height",r.$menu.height()),r.$bsContainer.detach()})},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length)for(var i=0;i<t.selectpicker.view.visibleElements.length;i++){var s=t.selectpicker.current.data[i+t.selectpicker.view.position0],n=s.option;n&&(!0!==e&&t.setDisabled(s.index,s.disabled),t.setSelected(s.index,n.selected))}},setSelected:function(e,t){var i,s,n=this.selectpicker.main.elements[e],o=this.selectpicker.main.data[e],r=void 0!==this.activeIndex,l=this.activeIndex===e||t&&!this.multiple&&!r;o.selected=t,s=n.firstChild,t&&(this.selectedIndex=e),n.classList.toggle("selected",t),l?(this.focusItem(n,o),this.selectpicker.view.currentActive=n,this.activeIndex=e):this.defocusItem(n),s&&(s.classList.toggle("selected",t),t?s.setAttribute("aria-selected",!0):this.multiple?s.setAttribute("aria-selected",!1):s.removeAttribute("aria-selected")),l||r||!t||void 0===this.prevActiveIndex||(i=this.selectpicker.main.elements[this.prevActiveIndex],this.defocusItem(i))},setDisabled:function(e,t){var i,s=this.selectpicker.main.elements[e];this.selectpicker.main.data[e].disabled=t,i=s.firstChild,s.classList.toggle(j.DISABLED,t),i&&("4"===M.major&&i.classList.toggle(j.DISABLED,t),t?(i.setAttribute("aria-disabled",t),i.setAttribute("tabindex",-1)):(i.removeAttribute("aria-disabled"),i.setAttribute("tabindex",0)))},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var e=this;this.isDisabled()?(this.$newElement[0].classList.add(j.DISABLED),this.$button.addClass(j.DISABLED).attr("tabindex",-1).attr("aria-disabled",!0)):(this.$button[0].classList.contains(j.DISABLED)&&(this.$newElement[0].classList.remove(j.DISABLED),this.$button.removeClass(j.DISABLED).attr("aria-disabled",!1)),-1!=this.$button.attr("tabindex")||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.on("click",function(){return!e.isDisabled()})},tabIndex:function(){this.$element.data("tabindex")!==this.$element.attr("tabindex")&&-98!==this.$element.attr("tabindex")&&"-98"!==this.$element.attr("tabindex")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex"))),this.$element.attr("tabindex",-98)},clickListener:function(){var C=this,t=z(document);function e(){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$menuInner.trigger("focus")}function i(){C.dropdown&&C.dropdown._popper&&C.dropdown._popper.state.isCreated?e():requestAnimationFrame(i)}t.data("spaceSelect",!1),this.$button.on("keyup",function(e){/(32)/.test(e.keyCode.toString(10))&&t.data("spaceSelect")&&(e.preventDefault(),t.data("spaceSelect",!1))}),this.$newElement.on("show.bs.dropdown",function(){3<M.major&&!C.dropdown&&(C.dropdown=C.$button.data("bs.dropdown"),C.dropdown._menu=C.$menu[0])}),this.$button.on("click.bs.dropdown.data-api",function(){C.$newElement.hasClass(j.SHOW)||C.setSize()}),this.$element.on("shown"+U,function(){C.$menuInner[0].scrollTop!==C.selectpicker.view.scrollTop&&(C.$menuInner[0].scrollTop=C.selectpicker.view.scrollTop),3<M.major?requestAnimationFrame(i):e()}),this.$menuInner.on("mouseenter","li a",function(e){var t=this.parentElement,i=C.isVirtual()?C.selectpicker.view.position0:0,s=Array.prototype.indexOf.call(t.parentElement.children,t),n=C.selectpicker.current.data[s+i];C.focusItem(t,n,!0)}),this.$menuInner.on("click","li a",function(e,t){var i=z(this),s=C.$element[0],n=C.isVirtual()?C.selectpicker.view.position0:0,o=C.selectpicker.current.data[i.parent().index()+n],r=o.index,l=O(s),a=s.selectedIndex,c=s.options[a],d=!0;if(C.multiple&&1!==C.options.maxOptions&&e.stopPropagation(),e.preventDefault(),!C.isDisabled()&&!i.parent().hasClass(j.DISABLED)){var h=C.$element.find("option"),p=o.option,u=z(p),f=p.selected,m=u.parent("optgroup"),v=m.find("option"),g=C.options.maxOptions,b=m.data("maxOptions")||!1;if(r===C.activeIndex&&(t=!0),t||(C.prevActiveIndex=C.activeIndex,C.activeIndex=void 0),C.multiple){if(p.selected=!f,C.setSelected(r,!f),i.trigger("blur"),!1!==g||!1!==b){var w=g<h.filter(":selected").length,I=b<m.find("option:selected").length;if(g&&w||b&&I)if(g&&1==g){h.prop("selected",!1),u.prop("selected",!0);for(var x=0;x<h.length;x++)C.setSelected(x,!1);C.setSelected(r,!0)}else if(b&&1==b){m.find("option:selected").prop("selected",!1),u.prop("selected",!0);for(x=0;x<v.length;x++){p=v[x];C.setSelected(h.index(p),!1)}C.setSelected(r,!0)}else{var k="string"==typeof C.options.maxOptionsText?[C.options.maxOptionsText,C.options.maxOptionsText]:C.options.maxOptionsText,$="function"==typeof k?k(g,b):k,y=$[0].replace("{n}",g),S=$[1].replace("{n}",b),E=z('<div class="notify"></div>');$[2]&&(y=y.replace("{var}",$[2][1<g?0:1]),S=S.replace("{var}",$[2][1<b?0:1])),u.prop("selected",!1),C.$menu.append(E),g&&w&&(E.append(z("<div>"+y+"</div>")),d=!1,C.$element.trigger("maxReached"+U)),b&&I&&(E.append(z("<div>"+S+"</div>")),d=!1,C.$element.trigger("maxReachedGrp"+U)),setTimeout(function(){C.setSelected(r,!1)},10),E.delay(750).fadeOut(300,function(){z(this).remove()})}}}else c.selected=!1,p.selected=!0,C.setSelected(r,!0);!C.multiple||C.multiple&&1===C.options.maxOptions?C.$button.trigger("focus"):C.options.liveSearch&&C.$searchbox.trigger("focus"),d&&(C.multiple||a!==s.selectedIndex)&&(T=[p.index,u.prop("selected"),l],C.$element.triggerNative("change"))}}),this.$menu.on("click","li."+j.DISABLED+" a, ."+j.POPOVERHEADER+", ."+j.POPOVERHEADER+" :not(.close)",function(e){e.currentTarget==this&&(e.preventDefault(),e.stopPropagation(),C.options.liveSearch&&!z(e.target).hasClass("close")?C.$searchbox.trigger("focus"):C.$button.trigger("focus"))}),this.$menuInner.on("click",".divider, .dropdown-header",function(e){e.preventDefault(),e.stopPropagation(),C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus")}),this.$menu.on("click","."+j.POPOVERHEADER+" .close",function(){C.$button.trigger("click")}),this.$searchbox.on("click",function(e){e.stopPropagation()}),this.$menu.on("click",".actions-btn",function(e){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus"),e.preventDefault(),e.stopPropagation(),z(this).hasClass("bs-select-all")?C.selectAll():C.deselectAll()}),this.$element.on("change"+U,function(){C.render(),C.$element.trigger("changed"+U,T),T=null}).on("focus"+U,function(){C.options.mobile||C.$button.trigger("focus")})},liveSearchListener:function(){var u=this,f=document.createElement("li");this.$button.on("click.bs.dropdown.data-api",function(){u.$searchbox.val()&&u.$searchbox.val("")}),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",function(e){e.stopPropagation()}),this.$searchbox.on("input propertychange",function(){var e=u.$searchbox.val();if(u.selectpicker.search.elements=[],u.selectpicker.search.data=[],e){var t=[],i=e.toUpperCase(),s={},n=[],o=u._searchStyle(),r=u.options.liveSearchNormalize;r&&(i=w(i)),u._$lisSelected=u.$menuInner.find(".selected");for(var l=0;l<u.selectpicker.main.data.length;l++){var a=u.selectpicker.main.data[l];s[l]||(s[l]=k(a,i,o,r)),s[l]&&void 0!==a.headerIndex&&-1===n.indexOf(a.headerIndex)&&(0<a.headerIndex&&(s[a.headerIndex-1]=!0,n.push(a.headerIndex-1)),s[a.headerIndex]=!0,n.push(a.headerIndex),s[a.lastIndex+1]=!0),s[l]&&"optgroup-label"!==a.type&&n.push(l)}l=0;for(var c=n.length;l<c;l++){var d=n[l],h=n[l-1],p=(a=u.selectpicker.main.data[d],u.selectpicker.main.data[h]);("divider"!==a.type||"divider"===a.type&&p&&"divider"!==p.type&&c-1!==l)&&(u.selectpicker.search.data.push(a),t.push(u.selectpicker.main.elements[d]))}u.activeIndex=void 0,u.noScroll=!0,u.$menuInner.scrollTop(0),u.selectpicker.search.elements=t,u.createView(!0),t.length||(f.className="no-results",f.innerHTML=u.options.noneResultsText.replace("{0}",'"'+E(e)+'"'),u.$menuInner[0].firstChild.appendChild(f))}else u.$menuInner.scrollTop(0),u.createView(!1)})},_searchStyle:function(){return this.options.liveSearchStyle||"contains"},val:function(e){var t=this.$element[0];if(void 0===e)return this.$element.val();var i=O(t);if(T=[null,null,i],this.$element.val(e).trigger("changed"+U,T),this.$newElement.hasClass(j.SHOW))if(this.multiple)this.setOptionStatus(!0);else{var s=(t.options[t.selectedIndex]||{}).liIndex;"number"==typeof s&&(this.setSelected(this.selectedIndex,!1),this.setSelected(s,!0))}return this.render(),T=null,this.$element},changeAll:function(e){if(this.multiple){void 0===e&&(e=!0);var t=this.$element[0],i=0,s=0,n=O(t);t.classList.add("bs-select-hidden");for(var o=0,r=this.selectpicker.current.elements.length;o<r;o++){var l=this.selectpicker.current.data[o],a=l.option;a&&!l.disabled&&"divider"!==l.type&&(l.selected&&i++,(a.selected=e)&&s++)}t.classList.remove("bs-select-hidden"),i!==s&&(this.setOptionStatus(),T=[null,null,n],this.$element.triggerNative("change"))}},selectAll:function(){return this.changeAll(!0)},deselectAll:function(){return this.changeAll(!1)},toggle:function(e){(e=e||window.event)&&e.stopPropagation(),this.$button.trigger("click.bs.dropdown.data-api")},keydown:function(e){var t,i,s,n,o,r=z(this),l=r.hasClass("dropdown-toggle"),a=(l?r.closest(".dropdown"):r.closest(V.MENU)).data("this"),c=a.findLis(),d=!1,h=e.which===H&&!l&&!a.options.selectOnTab,p=_.test(e.which)||h,u=a.$menuInner[0].scrollTop,f=!0===a.isVirtual()?a.selectpicker.view.position0:0;if(!(i=a.$newElement.hasClass(j.SHOW))&&(p||48<=e.which&&e.which<=57||96<=e.which&&e.which<=105||65<=e.which&&e.which<=90)&&(a.$button.trigger("click.bs.dropdown.data-api"),a.options.liveSearch))a.$searchbox.trigger("focus");else{if(e.which===L&&i&&(e.preventDefault(),a.$button.trigger("click.bs.dropdown.data-api").trigger("focus")),p){if(!c.length)return;-1!==(t=(s=a.selectpicker.main.elements[a.activeIndex])?Array.prototype.indexOf.call(s.parentElement.children,s):-1)&&a.defocusItem(s),e.which===B?(-1!==t&&t--,t+f<0&&(t+=c.length),a.selectpicker.view.canHighlight[t+f]||-1===(t=a.selectpicker.view.canHighlight.slice(0,t+f).lastIndexOf(!0)-f)&&(t=c.length-1)):(e.which===W||h)&&(++t+f>=a.selectpicker.view.canHighlight.length&&(t=0),a.selectpicker.view.canHighlight[t+f]||(t=t+1+a.selectpicker.view.canHighlight.slice(t+f+1).indexOf(!0))),e.preventDefault();var m=f+t;e.which===B?0===f&&t===c.length-1?(a.$menuInner[0].scrollTop=a.$menuInner[0].scrollHeight,m=a.selectpicker.current.elements.length-1):d=(o=(n=a.selectpicker.current.data[m]).position-n.height)<u:(e.which===W||h)&&(0===t?m=a.$menuInner[0].scrollTop=0:d=u<(o=(n=a.selectpicker.current.data[m]).position-a.sizeInfo.menuInnerHeight)),s=a.selectpicker.current.elements[m],a.activeIndex=a.selectpicker.current.data[m].index,a.focusItem(s),a.selectpicker.view.currentActive=s,d&&(a.$menuInner[0].scrollTop=o),a.options.liveSearch?a.$searchbox.trigger("focus"):r.trigger("focus")}else if(!r.is("input")&&!G.test(e.which)||e.which===D&&a.selectpicker.keydown.keyHistory){var v,g,b=[];e.preventDefault(),a.selectpicker.keydown.keyHistory+=C[e.which],a.selectpicker.keydown.resetKeyHistory.cancel&&clearTimeout(a.selectpicker.keydown.resetKeyHistory.cancel),a.selectpicker.keydown.resetKeyHistory.cancel=a.selectpicker.keydown.resetKeyHistory.start(),g=a.selectpicker.keydown.keyHistory,/^(.)\1+$/.test(g)&&(g=g.charAt(0));for(var w=0;w<a.selectpicker.current.data.length;w++){var I=a.selectpicker.current.data[w];k(I,g,"startsWith",!0)&&a.selectpicker.view.canHighlight[w]&&b.push(I.index)}if(b.length){var x=0;c.removeClass("active").find("a").removeClass("active"),1===g.length&&(-1===(x=b.indexOf(a.activeIndex))||x===b.length-1?x=0:x++),v=b[x],d=0<u-(n=a.selectpicker.main.data[v]).position?(o=n.position-n.height,!0):(o=n.position-a.sizeInfo.menuInnerHeight,n.position>u+a.sizeInfo.menuInnerHeight),s=a.selectpicker.main.elements[v],a.activeIndex=b[x],a.focusItem(s),s&&s.firstChild.focus(),d&&(a.$menuInner[0].scrollTop=o),r.trigger("focus")}}i&&(e.which===D&&!a.selectpicker.keydown.keyHistory||e.which===N||e.which===H&&a.options.selectOnTab)&&(e.which!==D&&e.preventDefault(),a.options.liveSearch&&e.which===D||(a.$menuInner.find(".active a").trigger("click",!0),r.trigger("focus"),a.options.liveSearch||(e.preventDefault(),z(document).data("spaceSelect",!0))))}},mobile:function(){this.$element[0].classList.add("mobile-device")},refresh:function(){var e=z.extend({},this.options,this.$element.data());this.options=e,this.checkDisabled(),this.setStyle(),this.render(),this.createLi(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+U)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(U).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),z(window).off(U+"."+this.selectId)}};var X=z.fn.selectpicker;z.fn.selectpicker=Q,z.fn.selectpicker.Constructor=J,z.fn.selectpicker.noConflict=function(){return z.fn.selectpicker=X,this},z(document).off("keydown.bs.dropdown.data-api").on("keydown"+U,'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',J.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',function(e){e.stopPropagation()}),z(window).on("load"+U+".data-api",function(){z(".selectpicker").each(function(){var e=z(this);Q.call(e,e.data())})})}(e)});
+//# sourceMappingURL=bootstrap-select.min.js.map
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js b/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js
new file mode 100644
index 0000000..05e26af
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js
@@ -0,0 +1,406 @@
+/*!
+ * jQuery cxSelect
+ * @name jquery.cxselect.js
+ * @version 1.4.2
+ * @date 2017-09-26
+ * @author ciaoca
+ * @email ciaoca@gmail.com
+ * @site https://github.com/ciaoca/cxSelect
+ * @license Released under the MIT license
+ */
+(function(factory) {
+  if (typeof define === 'function' && define.amd) {
+    define(['jquery'], factory);
+  } else {
+    factory(window.jQuery || window.Zepto || window.$);
+  };
+}(function($) {
+  var cxSelect = function() {
+    var self = this;
+    var dom, settings, callback;
+
+    // 分配参数
+    for (var i = 0, l = arguments.length; i < l; i++) {
+      if (cxSelect.isJquery(arguments[i]) || cxSelect.isZepto(arguments[i])) {
+        dom = arguments[i];
+      } else if (cxSelect.isElement(arguments[i])) {
+        dom = $(arguments[i]);
+      } else if (typeof arguments[i] === 'function') {
+        callback = arguments[i];
+      } else if (typeof arguments[i] === 'object') {
+        settings = arguments[i];
+      };
+    };
+
+    var api = new cxSelect.init(dom, settings);
+
+    if (typeof callback === 'function') {
+      callback(api);
+    };
+
+    return api;
+  };
+
+  cxSelect.isElement = function(o){
+    if (o && (typeof HTMLElement === 'function' || typeof HTMLElement === 'object') && o instanceof HTMLElement) {
+      return true;
+    } else {
+      return (o && o.nodeType && o.nodeType === 1) ? true : false;
+    };
+  };
+
+  cxSelect.isJquery = function(o){
+    return (o && o.length && (typeof jQuery === 'function' || typeof jQuery === 'object') && o instanceof jQuery) ? true : false;
+  };
+
+  cxSelect.isZepto = function(o){
+    return (o && o.length && (typeof Zepto === 'function' || typeof Zepto === 'object') && Zepto.zepto.isZ(o)) ? true : false;
+  };
+
+  cxSelect.getIndex = function(n, required) {
+    return required ? n : n - 1;
+  };
+
+  cxSelect.getData = function(data, space) {
+    if (typeof space === 'string' && space.length) {
+      space = space.split('.');
+      for (var i = 0, l = space.length; i < l; i++) {
+        data = data[space[i]];
+      };
+    };
+    return data;
+  };
+
+  cxSelect.init = function(dom, settings) {
+    var self = this;
+
+    if (!cxSelect.isJquery(dom) && !cxSelect.isZepto(dom)) {return};
+
+    var theSelect = {
+      dom: {
+        box: dom
+      }
+    };
+
+    self.attach = cxSelect.attach.bind(theSelect);
+    self.detach = cxSelect.detach.bind(theSelect);
+    self.setOptions = cxSelect.setOptions.bind(theSelect);
+    self.clear = cxSelect.clear.bind(theSelect);
+
+    theSelect.changeEvent = function() {
+      cxSelect.selectChange.call(theSelect, this.className);
+    };
+
+    theSelect.settings = $.extend({}, $.cxSelect.defaults, settings, {
+      url: theSelect.dom.box.data('url'),
+      emptyStyle: theSelect.dom.box.data('emptyStyle'),
+      required: theSelect.dom.box.data('required'),
+      firstTitle: theSelect.dom.box.data('firstTitle'),
+      firstValue: theSelect.dom.box.data('firstValue'),
+      jsonSpace: theSelect.dom.box.data('jsonSpace'),
+      jsonName: theSelect.dom.box.data('jsonName'),
+      jsonValue: theSelect.dom.box.data('jsonValue'),
+      jsonSub: theSelect.dom.box.data('jsonSub')
+    });
+
+    var _dataSelects = theSelect.dom.box.data('selects');
+
+    if (typeof _dataSelects === 'string' && _dataSelects.length) {
+      theSelect.settings.selects = _dataSelects.split(',');
+    };
+
+    self.setOptions();
+    self.attach();
+
+    // 使用独立接口获取数据
+    if (!theSelect.settings.url && !theSelect.settings.data) {
+      cxSelect.start.apply(theSelect);
+
+    // 设置自定义数据
+    } else if ($.isArray(theSelect.settings.data)) {
+      cxSelect.start.call(theSelect, theSelect.settings.data);
+
+    // 设置 URL,通过 Ajax 获取数据
+    } else if (typeof theSelect.settings.url === 'string' && theSelect.settings.url.length) {
+      $.getJSON(theSelect.settings.url, function(json) {
+        cxSelect.start.call(theSelect, json);
+      });
+    };
+  };
+
+  // 设置参数
+  cxSelect.setOptions = function(opts) {
+    var self = this;
+
+    if (opts) {
+      $.extend(self.settings, opts);
+    };
+
+    // 初次或重设选择器组
+    if (!$.isArray(self.selectArray) || !self.selectArray.length || (opts && opts.selects)) {
+      self.selectArray = [];
+
+      if ($.isArray(self.settings.selects) && self.settings.selects.length) {
+        var _tempSelect;
+
+        for (var i = 0, l = self.settings.selects.length; i < l; i++) {
+          _tempSelect = self.dom.box.find('select.' + self.settings.selects[i]);
+
+          if (!_tempSelect || !_tempSelect.length) {break};
+
+          self.selectArray.push(_tempSelect);
+        };
+      };
+    };
+
+    if (opts) {
+      if (!$.isArray(opts.data) && typeof opts.url === 'string' && opts.url.length) {
+        $.getJSON(self.settings.url, function(json) {
+          cxSelect.start.call(self, json);
+        });
+
+      } else {
+        cxSelect.start.call(self, opts.data);
+      };
+    };
+  };
+
+  // 绑定
+  cxSelect.attach = function() {
+    var self = this;
+
+    if (!self.attachStatus) {
+      self.dom.box.on('change', 'select', self.changeEvent);
+    };
+
+    if (typeof self.attachStatus === 'boolean') {
+      cxSelect.start.call(self);
+    };
+
+    self.attachStatus = true;
+  };
+
+  // 移除绑定
+  cxSelect.detach = function() {
+    var self = this;
+    self.dom.box.off('change', 'select', self.changeEvent);
+    self.attachStatus = false;
+  };
+
+  // 清空选项
+  cxSelect.clear = function(index) {
+    var self = this;
+    var _style = {
+      display: '',
+      visibility: ''
+    };
+
+    index = isNaN(index) ? 0 : index;
+
+    // 清空后面的 select
+    for (var i = index, l = self.selectArray.length; i < l; i++) {
+      self.selectArray[i].empty().prop('disabled', true);
+
+      if (self.settings.emptyStyle === 'none') {
+        _style.display = 'none';
+      } else if (self.settings.emptyStyle === 'hidden') {
+        _style.visibility = 'hidden';
+      };
+
+      self.selectArray[i].css(_style);
+    };
+  };
+
+  cxSelect.start = function(data) {
+    var self = this;
+
+    if ($.isArray(data)) {
+      self.settings.data = cxSelect.getData(data, self.settings.jsonSpace);
+    };
+
+    if (!self.selectArray.length) {return};
+
+    // 保存默认值
+    for (var i = 0, l = self.selectArray.length; i < l; i++) {
+      if (typeof self.selectArray[i].attr('data-value') !== 'string' && self.selectArray[i][0].options.length) {
+        self.selectArray[i].attr('data-value', self.selectArray[i].val());
+      };
+    };
+
+    if (self.settings.data || (typeof self.selectArray[0].data('url') === 'string' && self.selectArray[0].data('url').length)) {
+      cxSelect.getOptionData.call(self, 0);
+    } else if (self.selectArray[0][0].options.length && typeof self.selectArray[0].attr('data-value') === 'string' && self.selectArray[0].attr('data-value').length) {
+      self.selectArray[0].val(self.selectArray[0].attr('data-value'));
+      cxSelect.getOptionData.call(self, 1);
+    } else {
+      self.selectArray[0].prop('disabled', false).css({
+        'display': '',
+        'visibility': ''
+      });
+    };
+  };
+
+  // 获取选项数据
+  cxSelect.getOptionData = function(index) {
+    var self = this;
+
+    if (typeof index !== 'number' || isNaN(index) || index < 0 || index >= self.selectArray.length) {return};
+
+    var _indexPrev = index - 1;
+    var _select = self.selectArray[index];
+    var _selectData;
+    var _valueIndex;
+    var _dataUrl = _select.data('url');
+    var _jsonSpace = typeof _select.data('jsonSpace') === 'undefined' ? self.settings.jsonSpace : _select.data('jsonSpace');
+    var _query = {};
+    var _queryName;
+    var _selectName;
+    var _selectValue;
+
+    cxSelect.clear.call(self, index);
+
+    // 使用独立接口
+    if (typeof _dataUrl === 'string' && _dataUrl.length) {
+      if (index > 0) {
+        for (var i = 0, j = 1; i < index; i++, j++) {
+          _queryName = self.selectArray[j].data('queryName');
+          _selectName = self.selectArray[i].attr('name');
+          _selectValue = self.selectArray[i].val();
+
+          if (typeof _queryName === 'string' && _queryName.length) {
+            _query[_queryName] = _selectValue;
+          } else if (typeof _selectName === 'string' && _selectName.length) {
+            _query[_selectName] = _selectValue;
+          };
+        };
+      };
+
+      $.getJSON(_dataUrl, _query, function(json) {
+        _selectData = cxSelect.getData(json, _jsonSpace);
+
+        cxSelect.buildOption.call(self, index, _selectData);
+      });
+
+    // 使用整合数据
+    } else if (self.settings.data && typeof self.settings.data === 'object') {
+      _selectData = self.settings.data;
+
+      for (var i = 0; i < index; i++) {
+        _valueIndex = cxSelect.getIndex(self.selectArray[i][0].selectedIndex, typeof self.selectArray[i].data('required') === 'boolean' ? self.selectArray[i].data('required') : self.settings.required);
+
+        if (typeof _selectData[_valueIndex] === 'object' && $.isArray(_selectData[_valueIndex][self.settings.jsonSub]) && _selectData[_valueIndex][self.settings.jsonSub].length) {
+          _selectData = _selectData[_valueIndex][self.settings.jsonSub];
+        } else {
+          _selectData = null;
+          break;
+        };
+      };
+
+      cxSelect.buildOption.call(self, index, _selectData);
+    };
+  };
+
+  // 构建选项列表
+  cxSelect.buildOption = function(index, data) {
+    var self = this;
+
+    var _select = self.selectArray[index];
+    var _required = typeof _select.data('required') === 'boolean' ? _select.data('required') : self.settings.required;
+    var _firstTitle = typeof _select.data('firstTitle') === 'undefined' ? self.settings.firstTitle : _select.data('firstTitle');
+    var _firstValue = typeof _select.data('firstValue') === 'undefined' ? self.settings.firstValue : _select.data('firstValue');
+    var _jsonName = typeof _select.data('jsonName') === 'undefined' ? self.settings.jsonName : _select.data('jsonName');
+    var _jsonValue = typeof _select.data('jsonValue') === 'undefined' ? self.settings.jsonValue : _select.data('jsonValue');
+
+    if (!$.isArray(data)) {return};
+
+    var _html = !_required ? '<option value="' + String(_firstValue) + '">' + String(_firstTitle) + '</option>' : '';
+
+    // 区分标题、值的数据
+    if (typeof _jsonName === 'string' && _jsonName.length) {
+      // 无值字段时使用标题作为值
+      if (typeof _jsonValue !== 'string' || !_jsonValue.length) {
+        _jsonValue = _jsonName;
+      };
+
+      for (var i = 0, l = data.length; i < l; i++) {
+        _html += '<option value="' + String(data[i][_jsonValue]) + '">' + String(data[i][_jsonName]) + '</option>';
+      };
+
+    // 数组即为值的数据
+    } else {
+      for (var i = 0, l = data.length; i < l; i++) {
+        _html += '<option value="' + String(data[i]) + '">' + String(data[i]) + '</option>';
+      };
+    };
+
+    _select.html(_html).prop('disabled', false).css({
+      'display': '',
+      'visibility': ''
+    });
+
+    // 初次加载设置默认值
+    if (typeof _select.attr('data-value') === 'string') {
+      _select.val(String(_select.attr('data-value'))).removeAttr('data-value');
+
+      if (_select[0].selectedIndex < 0) {
+        _select[0].options[0].selected = true;
+      };
+    };
+
+    if (_required || _select[0].selectedIndex > 0) {
+      _select.trigger('change');
+    };
+
+  };
+
+  // 改变选择时的处理
+  cxSelect.selectChange = function(name) {
+    var self = this;
+
+    if (typeof name !== 'string' || !name.length) {return};
+
+    var index;
+
+    name = name.replace(/\s+/g, ',');
+    name = ',' + name + ',';
+
+    // 获取当前 select 位置
+    for (var i = 0, l = self.selectArray.length; i < l; i++) {
+      if (name.indexOf(',' + self.settings.selects[i] + ',') > -1) {
+        index = i;
+        break;
+      };
+    };
+
+    if (typeof index === 'number' && index > -1) {
+      index += 1;
+      cxSelect.getOptionData.call(self, index);
+    };
+  };
+
+  $.cxSelect = function() {
+    return cxSelect.apply(this, arguments);
+  };
+
+  // 默认值
+  $.cxSelect.defaults = {
+    selects: [],            // 下拉选框组
+    url: null,              // 列表数据文件路径(URL)或数组数据
+    data: null,             // 自定义数据
+    emptyStyle: null,       // 无数据状态显示方式
+    required: false,        // 是否为必选
+    firstTitle: '请选择',    // 第一个选项的标题
+    firstValue: '',         // 第一个选项的值
+    jsonSpace: '',          // 数据命名空间
+    jsonName: 'n',          // 数据标题字段名称
+    jsonValue: '',          // 数据值字段名称
+    jsonSub: 's'            // 子集数据字段名称
+  };
+
+  $.fn.cxSelect = function(settings, callback) {
+    this.each(function(i) {
+      $.cxSelect(this, settings, callback);
+    });
+    return this;
+  };
+}));
diff --git a/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.min.js b/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.min.js
new file mode 100644
index 0000000..85433a1
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.min.js
@@ -0,0 +1,11 @@
+/*!
+ * jQuery cxSelect
+ * @name jquery.cxselect.js
+ * @version 1.4.2
+ * @date 2017-09-26
+ * @author ciaoca
+ * @email ciaoca@gmail.com
+ * @site https://github.com/ciaoca/cxSelect
+ * @license Released under the MIT license
+ */
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(window.jQuery||window.Zepto||window.$)}(function(a){var b=function(){var d,e,f,g,h,i;for(g=0,h=arguments.length;h>g;g++)b.isJquery(arguments[g])||b.isZepto(arguments[g])?d=arguments[g]:b.isElement(arguments[g])?d=a(arguments[g]):"function"==typeof arguments[g]?f=arguments[g]:"object"==typeof arguments[g]&&(e=arguments[g]);return i=new b.init(d,e),"function"==typeof f&&f(i),i};b.isElement=function(a){return a&&("function"==typeof HTMLElement||"object"==typeof HTMLElement)&&a instanceof HTMLElement?!0:a&&a.nodeType&&1===a.nodeType?!0:!1},b.isJquery=function(a){return a&&a.length&&("function"==typeof jQuery||"object"==typeof jQuery)&&a instanceof jQuery?!0:!1},b.isZepto=function(a){return a&&a.length&&("function"==typeof Zepto||"object"==typeof Zepto)&&Zepto.zepto.isZ(a)?!0:!1},b.getIndex=function(a,b){return b?a:a-1},b.getData=function(a,b){if("string"==typeof b&&b.length){b=b.split(".");for(var c=0,d=b.length;d>c;c++)a=a[b[c]]}return a},b.init=function(c,d){var f,g,e=this;(b.isJquery(c)||b.isZepto(c))&&(f={dom:{box:c}},e.attach=b.attach.bind(f),e.detach=b.detach.bind(f),e.setOptions=b.setOptions.bind(f),e.clear=b.clear.bind(f),f.changeEvent=function(){b.selectChange.call(f,this.className)},f.settings=a.extend({},a.cxSelect.defaults,d,{url:f.dom.box.data("url"),emptyStyle:f.dom.box.data("emptyStyle"),required:f.dom.box.data("required"),firstTitle:f.dom.box.data("firstTitle"),firstValue:f.dom.box.data("firstValue"),jsonSpace:f.dom.box.data("jsonSpace"),jsonName:f.dom.box.data("jsonName"),jsonValue:f.dom.box.data("jsonValue"),jsonSub:f.dom.box.data("jsonSub")}),g=f.dom.box.data("selects"),"string"==typeof g&&g.length&&(f.settings.selects=g.split(",")),e.setOptions(),e.attach(),f.settings.url||f.settings.data?a.isArray(f.settings.data)?b.start.call(f,f.settings.data):"string"==typeof f.settings.url&&f.settings.url.length&&a.getJSON(f.settings.url,function(a){b.start.call(f,a)}):b.start.apply(f))},b.setOptions=function(c){var e,f,g,d=this;if(c&&a.extend(d.settings,c),(!a.isArray(d.selectArray)||!d.selectArray.length||c&&c.selects)&&(d.selectArray=[],a.isArray(d.settings.selects)&&d.settings.selects.length))for(f=0,g=d.settings.selects.length;g>f&&(e=d.dom.box.find("select."+d.settings.selects[f]),e&&e.length);f++)d.selectArray.push(e);c&&(!a.isArray(c.data)&&"string"==typeof c.url&&c.url.length?a.getJSON(d.settings.url,function(a){b.start.call(d,a)}):b.start.call(d,c.data))},b.attach=function(){var a=this;a.attachStatus||a.dom.box.on("change","select",a.changeEvent),"boolean"==typeof a.attachStatus&&b.start.call(a),a.attachStatus=!0},b.detach=function(){var a=this;a.dom.box.off("change","select",a.changeEvent),a.attachStatus=!1},b.clear=function(a){var d,e,b=this,c={display:"",visibility:""};for(a=isNaN(a)?0:a,d=a,e=b.selectArray.length;e>d;d++)b.selectArray[d].empty().prop("disabled",!0),"none"===b.settings.emptyStyle?c.display="none":"hidden"===b.settings.emptyStyle&&(c.visibility="hidden"),b.selectArray[d].css(c)},b.start=function(c){var e,f,d=this;if(a.isArray(c)&&(d.settings.data=b.getData(c,d.settings.jsonSpace)),d.selectArray.length){for(e=0,f=d.selectArray.length;f>e;e++)"string"!=typeof d.selectArray[e].attr("data-value")&&d.selectArray[e][0].options.length&&d.selectArray[e].attr("data-value",d.selectArray[e].val());d.settings.data||"string"==typeof d.selectArray[0].data("url")&&d.selectArray[0].data("url").length?b.getOptionData.call(d,0):d.selectArray[0][0].options.length&&"string"==typeof d.selectArray[0].attr("data-value")&&d.selectArray[0].attr("data-value").length?(d.selectArray[0].val(d.selectArray[0].attr("data-value")),b.getOptionData.call(d,1)):d.selectArray[0].prop("disabled",!1).css({display:"",visibility:""})}},b.getOptionData=function(c){var f,g,h,i,j,k,l,m,n,o,p,d=this;if(!("number"!=typeof c||isNaN(c)||0>c||c>=d.selectArray.length))if(f=d.selectArray[c],i=f.data("url"),j="undefined"==typeof f.data("jsonSpace")?d.settings.jsonSpace:f.data("jsonSpace"),k={},b.clear.call(d,c),"string"==typeof i&&i.length){if(c>0)for(o=0,p=1;c>o;o++,p++)l=d.selectArray[p].data("queryName"),m=d.selectArray[o].attr("name"),n=d.selectArray[o].val(),"string"==typeof l&&l.length?k[l]=n:"string"==typeof m&&m.length&&(k[m]=n);a.getJSON(i,k,function(a){g=b.getData(a,j),b.buildOption.call(d,c,g)})}else if(d.settings.data&&"object"==typeof d.settings.data){for(g=d.settings.data,o=0;c>o;o++){if(h=b.getIndex(d.selectArray[o][0].selectedIndex,"boolean"==typeof d.selectArray[o].data("required")?d.selectArray[o].data("required"):d.settings.required),"object"!=typeof g[h]||!a.isArray(g[h][d.settings.jsonSub])||!g[h][d.settings.jsonSub].length){g=null;break}g=g[h][d.settings.jsonSub]}b.buildOption.call(d,c,g)}},b.buildOption=function(b,c){var k,l,m,d=this,e=d.selectArray[b],f="boolean"==typeof e.data("required")?e.data("required"):d.settings.required,g="undefined"==typeof e.data("firstTitle")?d.settings.firstTitle:e.data("firstTitle"),h="undefined"==typeof e.data("firstValue")?d.settings.firstValue:e.data("firstValue"),i="undefined"==typeof e.data("jsonName")?d.settings.jsonName:e.data("jsonName"),j="undefined"==typeof e.data("jsonValue")?d.settings.jsonValue:e.data("jsonValue");if(a.isArray(c)){if(k=f?"":'<option value="'+String(h)+'">'+String(g)+"</option>","string"==typeof i&&i.length)for("string"==typeof j&&j.length||(j=i),l=0,m=c.length;m>l;l++)k+='<option value="'+String(c[l][j])+'">'+String(c[l][i])+"</option>";else for(l=0,m=c.length;m>l;l++)k+='<option value="'+String(c[l])+'">'+String(c[l])+"</option>";e.html(k).prop("disabled",!1).css({display:"",visibility:""}),"string"==typeof e.attr("data-value")&&(e.val(String(e.attr("data-value"))).removeAttr("data-value"),e[0].selectedIndex<0&&(e[0].options[0].selected=!0)),(f||e[0].selectedIndex>0)&&e.trigger("change")}},b.selectChange=function(a){var d,e,f,c=this;if("string"==typeof a&&a.length){for(a=a.replace(/\s+/g,","),a=","+a+",",e=0,f=c.selectArray.length;f>e;e++)if(a.indexOf(","+c.settings.selects[e]+",")>-1){d=e;break}"number"==typeof d&&d>-1&&(d+=1,b.getOptionData.call(c,d))}},a.cxSelect=function(){return b.apply(this,arguments)},a.cxSelect.defaults={selects:[],url:null,data:null,emptyStyle:null,required:!1,firstTitle:"请选择",firstValue:"",jsonSpace:"",jsonName:"n",jsonValue:"",jsonSub:"s"},a.fn.cxSelect=function(b,c){return this.each(function(){a.cxSelect(this,b,c)}),this}});
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/layer/theme/default/layer.css b/src/main/resources/static/ajax/libs/layer/theme/default/layer.css
index 820b4a9..4a2f617 100644
--- a/src/main/resources/static/ajax/libs/layer/theme/default/layer.css
+++ b/src/main/resources/static/ajax/libs/layer/theme/default/layer.css
@@ -1 +1,922 @@
-.layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}html #layuicss-layer{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;border-radius:2px;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layer-anim{-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-00{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 15px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:5px 5px 0;padding:0 15px;border:1px solid #dedede;background-color:#fff;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#1E9FFF;background-color:#1E9FFF;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:8px 15px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:5px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#fff;border-color:#E9E7E7;color:#333}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95;border-color:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:230px;height:36px;margin:0 auto;line-height:30px;padding-left:10px;border:1px solid #e6e6e6;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px;padding:6px 10px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;overflow:hidden;cursor:pointer}.layui-layer-tab .layui-layer-title span.layui-this{height:43px;border-left:1px solid #eee;border-right:1px solid #eee;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.layui-this{display:block}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}}
\ No newline at end of file
+.layui-layer-imgbar, .layui-layer-imgtit a, .layui-layer-tab .layui-layer-title span, .layui-layer-title {
+    text-overflow: ellipsis;
+    white-space: nowrap
+}
+
+html #layuicss-layer {
+    display: none;
+    position: absolute;
+    width: 1989px
+}
+
+.layui-layer, .layui-layer-shade {
+    position: fixed;
+    _position: absolute;
+    pointer-events: auto
+}
+
+.layui-layer-shade {
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    _height: expression(document.body.offsetHeight+"px")
+}
+
+.layui-layer {
+    -webkit-overflow-scrolling: touch;
+    top: 150px;
+    left: 0;
+    margin: 0;
+    padding: 0;
+    background-color: #fff;
+    -webkit-background-clip: content;
+    border-radius: 2px;
+    box-shadow: 1px 1px 50px rgba(0, 0, 0, .3)
+}
+
+.layui-layer-close {
+    position: absolute
+}
+
+.layui-layer-content {
+    position: relative
+}
+
+.layui-layer-border {
+    border: 1px solid #B2B2B2;
+    border: 1px solid rgba(0, 0, 0, .1);
+    box-shadow: 1px 1px 5px rgba(0, 0, 0, .2)
+}
+
+.layui-layer-load {
+    background: url(loading-1.gif) center center no-repeat #eee
+}
+
+.layui-layer-ico {
+    background: url(icon.png) no-repeat
+}
+
+.layui-layer-btn a, .layui-layer-dialog .layui-layer-ico, .layui-layer-setwin a {
+    display: inline-block;
+    *display: inline;
+    *zoom: 1;
+    vertical-align: top
+}
+
+.layui-layer-move {
+    display: none;
+    position: fixed;
+    *position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    cursor: move;
+    opacity: 0;
+    filter: alpha(opacity=0);
+    background-color: #fff;
+    z-index: 2147483647
+}
+
+.layui-layer-resize {
+    position: absolute;
+    width: 15px;
+    height: 15px;
+    right: 0;
+    bottom: 0;
+    cursor: se-resize
+}
+
+.layer-anim {
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+    -webkit-animation-duration: .3s;
+    animation-duration: .3s
+}
+
+@-webkit-keyframes layer-bounceIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(.5);
+        transform: scale(.5)
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: scale(1);
+        transform: scale(1)
+    }
+}
+
+@keyframes layer-bounceIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(.5);
+        -ms-transform: scale(.5);
+        transform: scale(.5)
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: scale(1);
+        -ms-transform: scale(1);
+        transform: scale(1)
+    }
+}
+
+.layer-anim-00 {
+    -webkit-animation-name: layer-bounceIn;
+    animation-name: layer-bounceIn
+}
+
+@-webkit-keyframes layer-zoomInDown {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(.1) translateY(-2000px);
+        transform: scale(.1) translateY(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(.475) translateY(60px);
+        transform: scale(.475) translateY(60px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out
+    }
+}
+
+@keyframes layer-zoomInDown {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(.1) translateY(-2000px);
+        -ms-transform: scale(.1) translateY(-2000px);
+        transform: scale(.1) translateY(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(.475) translateY(60px);
+        -ms-transform: scale(.475) translateY(60px);
+        transform: scale(.475) translateY(60px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out
+    }
+}
+
+.layer-anim-01 {
+    -webkit-animation-name: layer-zoomInDown;
+    animation-name: layer-zoomInDown
+}
+
+@-webkit-keyframes layer-fadeInUpBig {
+    0% {
+        opacity: 0;
+        -webkit-transform: translateY(2000px);
+        transform: translateY(2000px)
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translateY(0);
+        transform: translateY(0)
+    }
+}
+
+@keyframes layer-fadeInUpBig {
+    0% {
+        opacity: 0;
+        -webkit-transform: translateY(2000px);
+        -ms-transform: translateY(2000px);
+        transform: translateY(2000px)
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translateY(0);
+        -ms-transform: translateY(0);
+        transform: translateY(0)
+    }
+}
+
+.layer-anim-02 {
+    -webkit-animation-name: layer-fadeInUpBig;
+    animation-name: layer-fadeInUpBig
+}
+
+@-webkit-keyframes layer-zoomInLeft {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(.1) translateX(-2000px);
+        transform: scale(.1) translateX(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(.475) translateX(48px);
+        transform: scale(.475) translateX(48px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out
+    }
+}
+
+@keyframes layer-zoomInLeft {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(.1) translateX(-2000px);
+        -ms-transform: scale(.1) translateX(-2000px);
+        transform: scale(.1) translateX(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(.475) translateX(48px);
+        -ms-transform: scale(.475) translateX(48px);
+        transform: scale(.475) translateX(48px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out
+    }
+}
+
+.layer-anim-03 {
+    -webkit-animation-name: layer-zoomInLeft;
+    animation-name: layer-zoomInLeft
+}
+
+@-webkit-keyframes layer-rollIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: translateX(-100%) rotate(-120deg);
+        transform: translateX(-100%) rotate(-120deg)
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translateX(0) rotate(0);
+        transform: translateX(0) rotate(0)
+    }
+}
+
+@keyframes layer-rollIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: translateX(-100%) rotate(-120deg);
+        -ms-transform: translateX(-100%) rotate(-120deg);
+        transform: translateX(-100%) rotate(-120deg)
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translateX(0) rotate(0);
+        -ms-transform: translateX(0) rotate(0);
+        transform: translateX(0) rotate(0)
+    }
+}
+
+.layer-anim-04 {
+    -webkit-animation-name: layer-rollIn;
+    animation-name: layer-rollIn
+}
+
+@keyframes layer-fadeIn {
+    0% {
+        opacity: 0
+    }
+    100% {
+        opacity: 1
+    }
+}
+
+.layer-anim-05 {
+    -webkit-animation-name: layer-fadeIn;
+    animation-name: layer-fadeIn
+}
+
+@-webkit-keyframes layer-shake {
+    0%, 100% {
+        -webkit-transform: translateX(0);
+        transform: translateX(0)
+    }
+    10%, 30%, 50%, 70%, 90% {
+        -webkit-transform: translateX(-10px);
+        transform: translateX(-10px)
+    }
+    20%, 40%, 60%, 80% {
+        -webkit-transform: translateX(10px);
+        transform: translateX(10px)
+    }
+}
+
+@keyframes layer-shake {
+    0%, 100% {
+        -webkit-transform: translateX(0);
+        -ms-transform: translateX(0);
+        transform: translateX(0)
+    }
+    10%, 30%, 50%, 70%, 90% {
+        -webkit-transform: translateX(-10px);
+        -ms-transform: translateX(-10px);
+        transform: translateX(-10px)
+    }
+    20%, 40%, 60%, 80% {
+        -webkit-transform: translateX(10px);
+        -ms-transform: translateX(10px);
+        transform: translateX(10px)
+    }
+}
+
+.layer-anim-06 {
+    -webkit-animation-name: layer-shake;
+    animation-name: layer-shake
+}
+
+@-webkit-keyframes fadeIn {
+    0% {
+        opacity: 0
+    }
+    100% {
+        opacity: 1
+    }
+}
+
+.layui-layer-title {
+    padding: 0 80px 0 20px;
+    height: 42px;
+    line-height: 42px;
+    border-bottom: 1px solid #eee;
+    font-size: 14px;
+    color: #333;
+    overflow: hidden;
+    background-color: #F8F8F8;
+    border-radius: 2px 2px 0 0
+}
+
+.layui-layer-setwin {
+    position: absolute;
+    right: 15px;
+    *right: 0;
+    top: 15px;
+    font-size: 0;
+    line-height: initial
+}
+
+.layui-layer-setwin a {
+    position: relative;
+    width: 16px;
+    height: 16px;
+    margin-left: 10px;
+    font-size: 12px;
+    _overflow: hidden
+}
+
+.layui-layer-setwin .layui-layer-min cite {
+    position: absolute;
+    width: 14px;
+    height: 2px;
+    left: 0;
+    top: 50%;
+    margin-top: -1px;
+    background-color: #2E2D3C;
+    cursor: pointer;
+    _overflow: hidden
+}
+
+.layui-layer-setwin .layui-layer-min:hover cite {
+    background-color: #2D93CA
+}
+
+.layui-layer-setwin .layui-layer-max {
+    background-position: -32px -40px
+}
+
+.layui-layer-setwin .layui-layer-max:hover {
+    background-position: -16px -40px
+}
+
+.layui-layer-setwin .layui-layer-maxmin {
+    background-position: -65px -40px
+}
+
+.layui-layer-setwin .layui-layer-maxmin:hover {
+    background-position: -49px -40px
+}
+
+.layui-layer-setwin .layui-layer-close1 {
+    background-position: 1px -40px;
+    cursor: pointer
+}
+
+.layui-layer-setwin .layui-layer-close1:hover {
+    opacity: .7
+}
+
+.layui-layer-setwin .layui-layer-close2 {
+    position: absolute;
+    right: -28px;
+    top: -28px;
+    width: 30px;
+    height: 30px;
+    margin-left: 0;
+    background-position: -149px -31px;
+    *right: -18px;
+    _display: none
+}
+
+.layui-layer-setwin .layui-layer-close2:hover {
+    background-position: -180px -31px
+}
+
+.layui-layer-btn {
+    text-align: right;
+    padding: 0 15px 12px;
+    pointer-events: auto;
+    user-select: none;
+    -webkit-user-select: none
+}
+
+.layui-layer-btn a {
+    height: 28px;
+    line-height: 28px;
+    margin: 5px 5px 0;
+    padding: 0 15px;
+    border: 1px solid #dedede;
+    background-color: #fff;
+    color: #333;
+    border-radius: 2px;
+    font-weight: 400;
+    cursor: pointer;
+    text-decoration: none
+}
+
+.layui-layer-btn a:hover {
+    opacity: .9;
+    text-decoration: none
+}
+
+.layui-layer-btn a:active {
+    opacity: .8
+}
+
+.layui-layer-btn .layui-layer-btn0 {
+    border-color: #1E9FFF;
+    background-color: #1E9FFF;
+    color: #fff
+}
+
+.layui-layer-btn-l {
+    text-align: left
+}
+
+.layui-layer-btn-c {
+    text-align: center
+}
+
+.layui-layer-dialog {
+    min-width: 260px
+}
+
+.layui-layer-dialog .layui-layer-content {
+    position: relative;
+    padding: 20px;
+    line-height: 24px;
+    word-break: break-all;
+    overflow: hidden;
+    font-size: 14px;
+    overflow-x: hidden;
+    overflow-y: auto
+}
+
+.layui-layer-dialog .layui-layer-content .layui-layer-ico {
+    position: absolute;
+    top: 16px;
+    left: 15px;
+    _left: -40px;
+    width: 30px;
+    height: 30px
+}
+
+.layui-layer-ico1 {
+    background-position: -30px 0
+}
+
+.layui-layer-ico2 {
+    background-position: -60px 0
+}
+
+.layui-layer-ico3 {
+    background-position: -90px 0
+}
+
+.layui-layer-ico4 {
+    background-position: -120px 0
+}
+
+.layui-layer-ico5 {
+    background-position: -150px 0
+}
+
+.layui-layer-ico6 {
+    background-position: -180px 0
+}
+
+.layui-layer-rim {
+    border: 6px solid #8D8D8D;
+    border: 6px solid rgba(0, 0, 0, .3);
+    border-radius: 5px;
+    box-shadow: none
+}
+
+.layui-layer-msg {
+    min-width: 180px;
+    border: 1px solid #D3D4D3;
+    box-shadow: none
+}
+
+.layui-layer-hui {
+    min-width: 100px;
+    background-color: #000;
+    filter: alpha(opacity=60);
+    background-color: rgba(0, 0, 0, .6);
+    color: #fff;
+    border: none
+}
+
+.layui-layer-hui .layui-layer-content {
+    padding: 12px 25px;
+    text-align: center
+}
+
+.layui-layer-dialog .layui-layer-padding {
+    padding: 20px 20px 20px 55px;
+    text-align: left
+}
+
+.layui-layer-page .layui-layer-content {
+    position: relative;
+    overflow: auto
+}
+
+.layui-layer-iframe .layui-layer-btn, .layui-layer-page .layui-layer-btn {
+    padding-top: 10px
+}
+
+.layui-layer-nobg {
+    background: 0 0
+}
+
+.layui-layer-iframe iframe {
+    display: block;
+    width: 100%
+}
+
+.layui-layer-loading {
+    border-radius: 100%;
+    background: 0 0;
+    box-shadow: none;
+    border: none
+}
+
+.layui-layer-loading .layui-layer-content {
+    width: 60px;
+    height: 24px;
+    background: url(loading-0.gif) no-repeat
+}
+
+.layui-layer-loading .layui-layer-loading1 {
+    width: 37px;
+    height: 37px;
+    background: url(loading-1.gif) no-repeat
+}
+
+.layui-layer-ico16, .layui-layer-loading .layui-layer-loading2 {
+    width: 32px;
+    height: 32px;
+    background: url(loading-2.gif) no-repeat
+}
+
+.layui-layer-tips {
+    background: 0 0;
+    box-shadow: none;
+    border: none
+}
+
+.layui-layer-tips .layui-layer-content {
+    position: relative;
+    line-height: 22px;
+    min-width: 12px;
+    padding: 8px 15px;
+    font-size: 12px;
+    _float: left;
+    border-radius: 2px;
+    box-shadow: 1px 1px 3px rgba(0, 0, 0, .2);
+    background-color: #000;
+    color: #fff
+}
+
+.layui-layer-tips .layui-layer-close {
+    right: -2px;
+    top: -1px
+}
+
+.layui-layer-tips i.layui-layer-TipsG {
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-width: 8px;
+    border-color: transparent;
+    border-style: dashed;
+    *overflow: hidden
+}
+
+.layui-layer-tips i.layui-layer-TipsB, .layui-layer-tips i.layui-layer-TipsT {
+    left: 5px;
+    border-right-style: solid;
+    border-right-color: #000
+}
+
+.layui-layer-tips i.layui-layer-TipsT {
+    bottom: -8px
+}
+
+.layui-layer-tips i.layui-layer-TipsB {
+    top: -8px
+}
+
+.layui-layer-tips i.layui-layer-TipsL, .layui-layer-tips i.layui-layer-TipsR {
+    top: 5px;
+    border-bottom-style: solid;
+    border-bottom-color: #000
+}
+
+.layui-layer-tips i.layui-layer-TipsR {
+    left: -8px
+}
+
+.layui-layer-tips i.layui-layer-TipsL {
+    right: -8px
+}
+
+.layui-layer-lan[type=dialog] {
+    min-width: 280px
+}
+
+.layui-layer-lan .layui-layer-title {
+    background: #4476A7;
+    color: #fff;
+    border: none
+}
+
+.layui-layer-lan .layui-layer-btn {
+    padding: 5px 10px 10px;
+    text-align: right;
+    border-top: 1px solid #E9E7E7
+}
+
+.layui-layer-lan .layui-layer-btn a {
+    background: #fff;
+    border-color: #E9E7E7;
+    color: #333
+}
+
+.layui-layer-lan .layui-layer-btn .layui-layer-btn1 {
+    background: #C9C5C5
+}
+
+.layui-layer-molv .layui-layer-title {
+    background: #009f95;
+    color: #fff;
+    border: none
+}
+
+.layui-layer-molv .layui-layer-btn a {
+    background: #009f95;
+    border-color: #009f95
+}
+
+.layui-layer-molv .layui-layer-btn .layui-layer-btn1 {
+    background: #92B8B1
+}
+
+.layui-layer-iconext {
+    background: url(icon-ext.png) no-repeat
+}
+
+.layui-layer-prompt .layui-layer-input {
+    display: block;
+    width: 230px;
+    height: 36px;
+    margin: 0 auto;
+    line-height: 30px;
+    padding-left: 10px;
+    border: 1px solid #e6e6e6;
+    color: #333
+}
+
+.layui-layer-prompt textarea.layui-layer-input {
+    width: 300px;
+    height: 100px;
+    line-height: 20px;
+    padding: 6px 10px
+}
+
+.layui-layer-prompt .layui-layer-content {
+    padding: 20px
+}
+
+.layui-layer-prompt .layui-layer-btn {
+    padding-top: 0
+}
+
+.layui-layer-tab {
+    box-shadow: 1px 1px 50px rgba(0, 0, 0, .4)
+}
+
+.layui-layer-tab .layui-layer-title {
+    padding-left: 0;
+    overflow: visible
+}
+
+.layui-layer-tab .layui-layer-title span {
+    position: relative;
+    float: left;
+    min-width: 80px;
+    max-width: 260px;
+    padding: 0 20px;
+    text-align: center;
+    overflow: hidden;
+    cursor: pointer
+}
+
+.layui-layer-tab .layui-layer-title span.layui-this {
+    height: 43px;
+    border-left: 1px solid #eee;
+    border-right: 1px solid #eee;
+    background-color: #fff;
+    z-index: 10
+}
+
+.layui-layer-tab .layui-layer-title span:first-child {
+    border-left: none
+}
+
+.layui-layer-tabmain {
+    line-height: 24px;
+    clear: both
+}
+
+.layui-layer-tabmain .layui-layer-tabli {
+    display: none
+}
+
+.layui-layer-tabmain .layui-layer-tabli.layui-this {
+    display: block
+}
+
+.layui-layer-photos {
+    -webkit-animation-duration: .8s;
+    animation-duration: .8s
+}
+
+.layui-layer-photos .layui-layer-content {
+    overflow: hidden;
+    text-align: center
+}
+
+.layui-layer-photos .layui-layer-phimg img {
+    position: relative;
+    width: 100%;
+    display: inline-block;
+    *display: inline;
+    *zoom: 1;
+    vertical-align: top
+}
+
+.layui-layer-imgbar, .layui-layer-imguide {
+    display: none
+}
+
+.layui-layer-imgnext, .layui-layer-imgprev {
+    position: absolute;
+    top: 50%;
+    width: 27px;
+    _width: 44px;
+    height: 44px;
+    margin-top: -22px;
+    outline: 0;
+    blr: expression(this.onFocus=this.blur())
+}
+
+.layui-layer-tab .layui-layer-content {
+    width: 100%
+}
+
+.layui-layer-imgprev {
+    left: 10px;
+    background-position: -5px -5px;
+    _background-position: -70px -5px
+}
+
+.layui-layer-imgprev:hover {
+    background-position: -33px -5px;
+    _background-position: -120px -5px
+}
+
+.layui-layer-imgnext {
+    right: 10px;
+    _right: 8px;
+    background-position: -5px -50px;
+    _background-position: -70px -50px
+}
+
+.layui-layer-imgnext:hover {
+    background-position: -33px -50px;
+    _background-position: -120px -50px
+}
+
+.layui-layer-imgbar {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    height: 32px;
+    line-height: 32px;
+    background-color: rgba(0, 0, 0, .8);
+    background-color: #000 \9;
+    filter: Alpha(opacity=80);
+    color: #fff;
+    overflow: hidden;
+    font-size: 0
+}
+
+.layui-layer-imgtit * {
+    display: inline-block;
+    *display: inline;
+    *zoom: 1;
+    vertical-align: top;
+    font-size: 12px
+}
+
+.layui-layer-imgtit a {
+    max-width: 65%;
+    overflow: hidden;
+    color: #fff
+}
+
+.layui-layer-imgtit a:hover {
+    color: #fff;
+    text-decoration: underline
+}
+
+.layui-layer-imgtit em {
+    padding-left: 10px;
+    font-style: normal
+}
+
+@-webkit-keyframes layer-bounceOut {
+    100% {
+        opacity: 0;
+        -webkit-transform: scale(.7);
+        transform: scale(.7)
+    }
+    30% {
+        -webkit-transform: scale(1.05);
+        transform: scale(1.05)
+    }
+    0% {
+        -webkit-transform: scale(1);
+        transform: scale(1)
+    }
+}
+
+@keyframes layer-bounceOut {
+    100% {
+        opacity: 0;
+        -webkit-transform: scale(.7);
+        -ms-transform: scale(.7);
+        transform: scale(.7)
+    }
+    30% {
+        -webkit-transform: scale(1.05);
+        -ms-transform: scale(1.05);
+        transform: scale(1.05)
+    }
+    0% {
+        -webkit-transform: scale(1);
+        -ms-transform: scale(1);
+        transform: scale(1)
+    }
+}
+
+.layer-anim-close {
+    -webkit-animation-name: layer-bounceOut;
+    animation-name: layer-bounceOut;
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+    -webkit-animation-duration: .2s;
+    animation-duration: .2s
+}
+
+@media screen and (max-width: 1100px) {
+    .layui-layer-iframe {
+        overflow-y: auto;
+        -webkit-overflow-scrolling: touch
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/select/select2.css b/src/main/resources/static/ajax/libs/select/select2.css
deleted file mode 100644
index 6c26a55..0000000
--- a/src/main/resources/static/ajax/libs/select/select2.css
+++ /dev/null
@@ -1 +0,0 @@
-.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:0;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:0;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0!important;clip:rect(0 0 0 0)!important;height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:0;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top,white 50%,#eee 100%);background-image:-o-linear-gradient(top,white 50%,#eee 100%);background-image:linear-gradient(to bottom,white 50%,#eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF',endColorstr='#FFEEEEEE',GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:0;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top,#eee 50%,#ccc 100%);background-image:-o-linear-gradient(top,#eee 50%,#ccc 100%);background-image:linear-gradient(to bottom,#eee 50%,#ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE',endColorstr='#FFCCCCCC',GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:0;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:0}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:0;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top,white 0,#eee 50%);background-image:-o-linear-gradient(top,white 0,#eee 50%);background-image:linear-gradient(to bottom,white 0,#eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF',endColorstr='#FFEEEEEE',GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top,#eee 50%,white 100%);background-image:-o-linear-gradient(top,#eee 50%,white 100%);background-image:linear-gradient(to bottom,#eee 50%,white 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE',endColorstr='#FFFFFFFF',GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:0;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:white;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:0}.select2-container--classic .select2-dropdown--below{border-top:0}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:white}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/select/select2.js b/src/main/resources/static/ajax/libs/select/select2.js
deleted file mode 100644
index 4efab47..0000000
--- a/src/main/resources/static/ajax/libs/select/select2.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*!
- * Select2 4.0.3
- * https://select2.github.io
- *
- * Released under the MIT license
- * https://github.com/select2/select2/blob/master/LICENSE.md
- */
-(function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a)}else{if(typeof exports==="object"){a(require("jquery"))}else{a(jQuery)}}}(function(c){var b=(function(){if(c&&c.fn&&c.fn.select2&&c.fn.select2.amd){var d=c.fn.select2.amd}var d;(function(){if(!d||!d.requirejs){if(!d){d={}}else{e=d}var f,e,g;(function(l){var o,h,s,t,k={},j={},z={},w={},r=Object.prototype.hasOwnProperty,p=[].slice,q=/\.js$/;function A(B,C){return r.call(B,C)}function u(E,C){var M,I,G,J,N,F,P,Q,L,K,H,O=C&&C.split("/"),D=z.map,B=(D&&D["*"])||{};if(E&&E.charAt(0)==="."){if(C){E=E.split("/");N=E.length-1;if(z.nodeIdCompat&&q.test(E[N])){E[N]=E[N].replace(q,"")}E=O.slice(0,O.length-1).concat(E);for(L=0;L<E.length;L+=1){H=E[L];if(H==="."){E.splice(L,1);L-=1}else{if(H===".."){if(L===1&&(E[2]===".."||E[0]==="..")){break}else{if(L>0){E.splice(L-1,2);L-=2}}}}}E=E.join("/")}else{if(E.indexOf("./")===0){E=E.substring(2)}}}if((O||B)&&D){M=E.split("/");for(L=M.length;L>0;L-=1){I=M.slice(0,L).join("/");if(O){for(K=O.length;K>0;K-=1){G=D[O.slice(0,K).join("/")];if(G){G=G[I];if(G){J=G;F=L;break}}}}if(J){break}if(!P&&B&&B[I]){P=B[I];Q=L}}if(!J&&P){J=P;F=Q}if(J){M.splice(0,F,J);E=M.join("/")}}return E}function y(B,C){return function(){var D=p.call(arguments,0);if(typeof D[0]!=="string"&&D.length===1){D.push(null)}return h.apply(l,D.concat([B,C]))}}function v(B){return function(C){return u(C,B)}}function m(B){return function(C){k[B]=C}}function n(C){if(A(j,C)){var B=j[C];delete j[C];w[C]=true;o.apply(l,B)}if(!A(k,C)&&!A(w,C)){throw new Error("No "+C)}return k[C]}function x(C){var D,B=C?C.indexOf("!"):-1;if(B>-1){D=C.substring(0,B);C=C.substring(B+1,C.length)}return[D,C]}s=function(C,B){var D,F=x(C),E=F[0];C=F[1];if(E){E=u(E,B);D=n(E)}if(E){if(D&&D.normalize){C=D.normalize(C,v(B))}else{C=u(C,B)}}else{C=u(C,B);F=x(C);E=F[0];C=F[1];if(E){D=n(E)}}return{f:E?E+"!"+C:C,n:C,pr:E,p:D}};function i(B){return function(){return(z&&z.config&&z.config[B])||{}}}t={require:function(B){return y(B)},exports:function(B){var C=k[B];if(typeof C!=="undefined"){return C}else{return(k[B]={})}},module:function(B){return{id:B,uri:"",exports:k[B],config:i(B)}}};o=function(C,M,L,K){var F,J,G,B,E,H=[],D=typeof L,I;K=K||C;if(D==="undefined"||D==="function"){M=!M.length&&L.length?["require","exports","module"]:M;for(E=0;E<M.length;E+=1){B=s(M[E],K);J=B.f;if(J==="require"){H[E]=t.require(C)}else{if(J==="exports"){H[E]=t.exports(C);I=true}else{if(J==="module"){F=H[E]=t.module(C)}else{if(A(k,J)||A(j,J)||A(w,J)){H[E]=n(J)}else{if(B.p){B.p.load(B.n,y(K,true),m(J),{});H[E]=k[J]}else{throw new Error(C+" missing "+J)}}}}}}G=L?L.apply(k[C],H):undefined;if(C){if(F&&F.exports!==l&&F.exports!==k[C]){k[C]=F.exports}else{if(G!==l||!I){k[C]=G}}}}else{if(C){k[C]=L}}};f=e=h=function(E,F,B,C,D){if(typeof E==="string"){if(t[E]){return t[E](F)}return n(s(E,F).f)}else{if(!E.splice){z=E;if(z.deps){h(z.deps,z.callback)}if(!F){return}if(F.splice){E=F;F=B;B=null}else{E=l}}}F=F||function(){};if(typeof B==="function"){B=C;C=D}if(C){o(l,E,F,B)}else{setTimeout(function(){o(l,E,F,B)},4)}return h};h.config=function(B){return h(B)};f._defined=k;g=function(B,C,D){if(typeof B!=="string"){throw new Error("See almond README: incorrect module build, no module name")}if(!C.splice){D=C;C=[]}if(!A(k,B)&&!A(j,B)){j[B]=[B,C,D]}};g.amd={jQuery:true}}());d.requirejs=f;d.require=e;d.define=g}}());d.define("almond",function(){});d.define("jquery",[],function(){var e=c||$;if(e==null&&console&&console.error){console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page.")}return e});d.define("select2/utils",["jquery"],function(h){var g={};g.Extend=function(m,j){var k={}.hasOwnProperty;function i(){this.constructor=m}for(var l in j){if(k.call(j,l)){m[l]=j[l]}}i.prototype=j.prototype;m.prototype=new i();m.__super__=j.prototype;return m};function f(n){var l=n.prototype;var k=[];for(var j in l){var i=l[j];if(typeof i!=="function"){continue}if(j==="constructor"){continue}k.push(j)}return k}g.Decorate=function(q,p){var o=f(p);var n=f(q);function t(){var u=Array.prototype.unshift;var v=p.prototype.constructor.length;var m=q.prototype.constructor;if(v>0){u.call(arguments,q.prototype.constructor);m=p.prototype.constructor}m.apply(this,arguments)}p.displayName=q.displayName;function i(){this.constructor=t}t.prototype=new i();for(var l=0;l<n.length;l++){var j=n[l];t.prototype[j]=q.prototype[j]}var s=function(m){var u=function(){};if(m in t.prototype){u=t.prototype[m]}var v=p.prototype[m];return function(){var w=Array.prototype.unshift;w.call(arguments,u);return v.apply(this,arguments)}};for(var r=0;r<o.length;r++){var k=o[r];t.prototype[k]=s(k)}return t};var e=function(){this.listeners={}};e.prototype.on=function(i,j){this.listeners=this.listeners||{};if(i in this.listeners){this.listeners[i].push(j)}else{this.listeners[i]=[j]}};e.prototype.trigger=function(i){var k=Array.prototype.slice;var j=k.call(arguments,1);this.listeners=this.listeners||{};if(j==null){j=[]}if(j.length===0){j.push({})}j[0]._type=i;if(i in this.listeners){this.invoke(this.listeners[i],k.call(arguments,1))}if("*" in this.listeners){this.invoke(this.listeners["*"],arguments)}};e.prototype.invoke=function(l,m){for(var k=0,j=l.length;k<j;k++){l[k].apply(this,m)}};g.Observable=e;g.generateChars=function(m){var l="";for(var k=0;k<m;k++){var j=Math.floor(Math.random()*36);l+=j.toString(36)}return l};g.bind=function(j,i){return function(){j.apply(i,arguments)}};g._convertData=function(o){for(var n in o){var m=n.split("-");var i=o;if(m.length===1){continue}for(var j=0;j<m.length;j++){var l=m[j];l=l.substring(0,1).toLowerCase()+l.substring(1);if(!(l in i)){i[l]={}}if(j==m.length-1){i[l]=o[n]}i=i[l]}delete o[n]}return o};g.hasScroll=function(k,m){var l=h(m);var j=m.style.overflowX;var i=m.style.overflowY;if(j===i&&(i==="hidden"||i==="visible")){return false}if(j==="scroll"||i==="scroll"){return true}return(l.innerHeight()<m.scrollHeight||l.innerWidth()<m.scrollWidth)};g.escapeMarkup=function(i){var j={"\\":"&#92;","&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#47;"};if(typeof i!=="string"){return i}return String(i).replace(/[&<>"'\/\\]/g,function(k){return j[k]})};g.appendMany=function(i,k){if(h.fn.jquery.substr(0,3)==="1.7"){var j=h();h.map(k,function(l){j=j.add(l)});k=j}i.append(k)};return g});d.define("select2/results",["jquery","./utils"],function(g,f){function e(h,i,j){this.$element=h;this.data=j;this.options=i;e.__super__.constructor.call(this)}f.Extend(e,f.Observable);e.prototype.render=function(){var h=g('<ul class="select2-results__options" role="tree"></ul>');if(this.options.get("multiple")){h.attr("aria-multiselectable","true")}this.$results=h;return h};e.prototype.clear=function(){this.$results.empty()};e.prototype.displayMessage=function(k){var h=this.options.get("escapeMarkup");this.clear();this.hideLoading();var i=g('<li role="treeitem" aria-live="assertive" class="select2-results__option"></li>');var j=this.options.get("translations").get(k.message);i.append(h(j(k.args)));i[0].className+=" select2-results__message";this.$results.append(i)};e.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()};e.prototype.append=function(j){this.hideLoading();var h=[];if(j.results==null||j.results.length===0){if(this.$results.children().length===0){this.trigger("results:message",{message:"noResults"})}return}j.results=this.sort(j.results);for(var l=0;l<j.results.length;l++){var i=j.results[l];var k=this.option(i);h.push(k)}this.$results.append(h)};e.prototype.position=function(h,i){var j=i.find(".select2-results");j.append(h)};e.prototype.sort=function(h){var i=this.options.get("sorter");return i(h)};e.prototype.highlightFirstItem=function(){var h=this.$results.find(".select2-results__option[aria-selected]");var i=h.filter("[aria-selected=true]");if(i.length>0){i.first().trigger("mouseenter")}else{h.first().trigger("mouseenter")}this.ensureHighlightVisible()};e.prototype.setClasses=function(){var h=this;this.data.current(function(k){var j=g.map(k,function(l){return l.id.toString()});var i=h.$results.find(".select2-results__option[aria-selected]");i.each(function(){var m=g(this);var l=g.data(this,"data");var n=""+l.id;if((l.element!=null&&l.element.selected)||(l.element==null&&g.inArray(n,j)>-1)){m.attr("aria-selected","true")}else{m.attr("aria-selected","false")}})})};e.prototype.showLoading=function(j){this.hideLoading();var i=this.options.get("translations").get("searching");var k={disabled:true,loading:true,text:i(j)};var h=this.option(k);h.className+=" loading-results";this.$results.prepend(h)};e.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()};e.prototype.option=function(l){var m=document.createElement("li");m.className="select2-results__option";var t={role:"treeitem","aria-selected":"false"};if(l.disabled){delete t["aria-selected"];t["aria-disabled"]="true"}if(l.id==null){delete t["aria-selected"]}if(l._resultId!=null){m.id=l._resultId}if(l.title){m.title=l.title}if(l.children){t.role="group";t["aria-label"]=l.text;delete t["aria-selected"]}for(var n in t){var k=t[n];m.setAttribute(n,k)}if(l.children){var h=g(m);var r=document.createElement("strong");r.className="select2-results__group";var q=g(r);this.template(l,r);var s=[];for(var p=0;p<l.children.length;p++){var j=l.children[p];var i=this.option(j);s.push(i)}var o=g("<ul></ul>",{"class":"select2-results__options select2-results__options--nested"});o.append(s);h.append(r);h.append(o)}else{this.template(l,m)}g.data(m,"data",l);return m};e.prototype.bind=function(h,j){var i=this;var k=h.id+"-results";this.$results.attr("id",k);h.on("results:all",function(l){i.clear();i.append(l.data);if(h.isOpen()){i.setClasses();i.highlightFirstItem()}});h.on("results:append",function(l){i.append(l.data);if(h.isOpen()){i.setClasses()}});h.on("query",function(l){i.hideMessages();i.showLoading(l)});h.on("select",function(){if(!h.isOpen()){return}i.setClasses();i.highlightFirstItem()});h.on("unselect",function(){if(!h.isOpen()){return}i.setClasses();i.highlightFirstItem()});h.on("open",function(){i.$results.attr("aria-expanded","true");i.$results.attr("aria-hidden","false");i.setClasses();i.ensureHighlightVisible()});h.on("close",function(){i.$results.attr("aria-expanded","false");i.$results.attr("aria-hidden","true");i.$results.removeAttr("aria-activedescendant")});h.on("results:toggle",function(){var l=i.getHighlightedResults();if(l.length===0){return}l.trigger("mouseup")});h.on("results:select",function(){var l=i.getHighlightedResults();if(l.length===0){return}var m=l.data("data");if(l.attr("aria-selected")=="true"){i.trigger("close",{})}else{i.trigger("select",{data:m})}});h.on("results:previous",function(){var n=i.getHighlightedResults();var m=i.$results.find("[aria-selected]");var p=m.index(n);if(p===0){return}var l=p-1;if(n.length===0){l=0}var o=m.eq(l);o.trigger("mouseenter");var s=i.$results.offset().top;var r=o.offset().top;var q=i.$results.scrollTop()+(r-s);if(l===0){i.$results.scrollTop(0)}else{if(r-s<0){i.$results.scrollTop(q)}}});h.on("results:next",function(){var n=i.getHighlightedResults();var m=i.$results.find("[aria-selected]");var p=m.index(n);var l=p+1;if(l>=m.length){return}var o=m.eq(l);o.trigger("mouseenter");var s=i.$results.offset().top+i.$results.outerHeight(false);var r=o.offset().top+o.outerHeight(false);var q=i.$results.scrollTop()+r-s;if(l===0){i.$results.scrollTop(0)}else{if(r>s){i.$results.scrollTop(q)}}});h.on("results:focus",function(l){l.element.addClass("select2-results__option--highlighted")});h.on("results:message",function(l){i.displayMessage(l)});if(g.fn.mousewheel){this.$results.on("mousewheel",function(o){var n=i.$results.scrollTop();var l=i.$results.get(0).scrollHeight-n+o.deltaY;var p=o.deltaY>0&&n-o.deltaY<=0;var m=o.deltaY<0&&l<=i.$results.height();if(p){i.$results.scrollTop(0);o.preventDefault();o.stopPropagation()}else{if(m){i.$results.scrollTop(i.$results.get(0).scrollHeight-i.$results.height());o.preventDefault();o.stopPropagation()}}})}this.$results.on("mouseup",".select2-results__option[aria-selected]",function(l){var n=g(this);var m=n.data("data");if(n.attr("aria-selected")==="true"){if(i.options.get("multiple")){i.trigger("unselect",{originalEvent:l,data:m})}else{i.trigger("close",{})}return}i.trigger("select",{originalEvent:l,data:m})});this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(l){var m=g(this).data("data");i.getHighlightedResults().removeClass("select2-results__option--highlighted");i.trigger("results:focus",{data:m,element:g(this)})})};e.prototype.getHighlightedResults=function(){var h=this.$results.find(".select2-results__option--highlighted");return h};e.prototype.destroy=function(){this.$results.remove()};e.prototype.ensureHighlightVisible=function(){var i=this.getHighlightedResults();if(i.length===0){return}var h=this.$results.find("[aria-selected]");var j=h.index(i);var m=this.$results.offset().top;var l=i.offset().top;var k=this.$results.scrollTop()+(l-m);var n=l-m;k-=i.outerHeight(false)*2;if(j<=2){this.$results.scrollTop(0)}else{if(n>this.$results.outerHeight()||n<0){this.$results.scrollTop(k)}}};e.prototype.template=function(i,j){var k=this.options.get("templateResult");var h=this.options.get("escapeMarkup");var l=k(i,j);if(l==null){j.style.display="none"}else{if(typeof l==="string"){j.innerHTML=h(l)}else{g(j).append(l)}}};return e});d.define("select2/keys",[],function(){var e={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46};return e});d.define("select2/selection/base",["jquery","../utils","../keys"],function(h,g,e){function f(i,j){this.$element=i;this.options=j;f.__super__.constructor.call(this)}g.Extend(f,g.Observable);f.prototype.render=function(){var i=h('<span class="select2-selection" role="combobox"  aria-haspopup="true" aria-expanded="false"></span>');this._tabindex=0;if(this.$element.data("old-tabindex")!=null){this._tabindex=this.$element.data("old-tabindex")}else{if(this.$element.attr("tabindex")!=null){this._tabindex=this.$element.attr("tabindex")}}i.attr("title",this.$element.attr("title"));i.attr("tabindex",this._tabindex);this.$selection=i;return i};f.prototype.bind=function(i,l){var k=this;var m=i.id+"-container";var j=i.id+"-results";this.container=i;this.$selection.on("focus",function(n){k.trigger("focus",n)});this.$selection.on("blur",function(n){k._handleBlur(n)});this.$selection.on("keydown",function(n){k.trigger("keypress",n);if(n.which===e.SPACE){n.preventDefault()}});i.on("results:focus",function(n){k.$selection.attr("aria-activedescendant",n.data._resultId)});i.on("selection:update",function(n){k.update(n.data)});i.on("open",function(){k.$selection.attr("aria-expanded","true");k.$selection.attr("aria-owns",j);k._attachCloseHandler(i)});i.on("close",function(){k.$selection.attr("aria-expanded","false");k.$selection.removeAttr("aria-activedescendant");k.$selection.removeAttr("aria-owns");k.$selection.focus();k._detachCloseHandler(i)});i.on("enable",function(){k.$selection.attr("tabindex",k._tabindex)});i.on("disable",function(){k.$selection.attr("tabindex","-1")})};f.prototype._handleBlur=function(i){var j=this;window.setTimeout(function(){if((document.activeElement==j.$selection[0])||(h.contains(j.$selection[0],document.activeElement))){return}j.trigger("blur",i)},1)};f.prototype._attachCloseHandler=function(i){var j=this;h(document.body).on("mousedown.select2."+i.id,function(n){var k=h(n.target);var l=k.closest(".select2");var m=h(".select2.select2-container--open");m.each(function(){var p=h(this);if(this==l[0]){return}var o=p.data("element");o.select2("close")})})};f.prototype._detachCloseHandler=function(i){h(document.body).off("mousedown.select2."+i.id)};f.prototype.position=function(i,k){var j=k.find(".selection");j.append(i)};f.prototype.destroy=function(){this._detachCloseHandler(this.container)};f.prototype.update=function(i){throw new Error("The `update` method must be defined in child classes.")};return f});d.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(h,f,g,e){function i(){i.__super__.constructor.apply(this,arguments)}g.Extend(i,f);i.prototype.render=function(){var j=i.__super__.render.call(this);j.addClass("select2-selection--single");j.html('<span class="select2-selection__rendered"></span><span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span>');return j};i.prototype.bind=function(j,l){var k=this;i.__super__.bind.apply(this,arguments);var m=j.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",m);this.$selection.attr("aria-labelledby",m);this.$selection.on("mousedown",function(n){if(n.which!==1){return}k.trigger("toggle",{originalEvent:n})});this.$selection.on("focus",function(n){});this.$selection.on("blur",function(n){});j.on("focus",function(n){if(!j.isOpen()){k.$selection.focus()}});j.on("selection:update",function(n){k.update(n.data)})};i.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()};i.prototype.display=function(m,k){var l=this.options.get("templateSelection");var j=this.options.get("escapeMarkup");return j(l(m,k))};i.prototype.selectionContainer=function(){return h("<span></span>")};i.prototype.update=function(l){if(l.length===0){this.clear();return}var j=l[0];var m=this.$selection.find(".select2-selection__rendered");var k=this.display(j,m);m.empty().append(k);m.prop("title",j.title||j.text)};return i});d.define("select2/selection/multiple",["jquery","./base","../utils"],function(h,e,g){function f(i,j){f.__super__.constructor.apply(this,arguments)}g.Extend(f,e);f.prototype.render=function(){var i=f.__super__.render.call(this);i.addClass("select2-selection--multiple");i.html('<ul class="select2-selection__rendered"></ul>');return i};f.prototype.bind=function(i,k){var j=this;f.__super__.bind.apply(this,arguments);this.$selection.on("click",function(l){j.trigger("toggle",{originalEvent:l})});this.$selection.on("click",".select2-selection__choice__remove",function(m){if(j.options.get("disabled")){return}var n=h(this);var l=n.parent();var o=l.data("data");j.trigger("unselect",{originalEvent:m,data:o})})};f.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()};f.prototype.display=function(l,j){var k=this.options.get("templateSelection");var i=this.options.get("escapeMarkup");return i(k(l,j))};f.prototype.selectionContainer=function(){var i=h('<li class="select2-selection__choice"><span class="select2-selection__choice__remove" role="presentation">&times;</span></li>');return i};f.prototype.update=function(m){this.clear();if(m.length===0){return}var j=[];for(var o=0;o<m.length;o++){var k=m[o];var i=this.selectionContainer();var l=this.display(k,i);i.append(l);i.prop("title",k.title||k.text);i.data("data",k);j.push(i)}var n=this.$selection.find(".select2-selection__rendered");g.appendMany(n,j)};return f});d.define("select2/selection/placeholder",["../utils"],function(f){function e(i,g,h){this.placeholder=this.normalizePlaceholder(h.get("placeholder"));i.call(this,g,h)}e.prototype.normalizePlaceholder=function(g,h){if(typeof h==="string"){h={id:"",text:h}}return h};e.prototype.createPlaceholder=function(h,i){var g=this.selectionContainer();g.html(this.display(i));g.addClass("select2-selection__placeholder").removeClass("select2-selection__choice");return g};e.prototype.update=function(k,j){var h=(j.length==1&&j[0].id!=this.placeholder.id);var i=j.length>1;if(i||h){return k.call(this,j)}this.clear();var g=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(g)};return e});d.define("select2/selection/allowClear",["jquery","../keys"],function(f,e){function g(){}g.prototype.bind=function(j,h,k){var i=this;j.call(this,h,k);if(this.placeholder==null){if(this.options.get("debug")&&window.console&&console.error){console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option.")}}this.$selection.on("mousedown",".select2-selection__clear",function(l){i._handleClear(l)});h.on("keypress",function(l){i._handleKeyboardClear(l,h)})};g.prototype._handleClear=function(i,h){if(this.options.get("disabled")){return}var l=this.$selection.find(".select2-selection__clear");if(l.length===0){return}h.stopPropagation();var k=l.data("data");for(var m=0;m<k.length;m++){var j={data:k[m]};this.trigger("unselect",j);if(j.prevented){return}}this.$element.val(this.placeholder.id).trigger("change");this.trigger("toggle",{})};g.prototype._handleKeyboardClear=function(j,i,h){if(h.isOpen()){return}if(i.which==e.DELETE||i.which==e.BACKSPACE){this._handleClear(i)}};g.prototype.update=function(j,i){j.call(this,i);if(this.$selection.find(".select2-selection__placeholder").length>0||i.length===0){return}var h=f('<span class="select2-selection__clear">&times;</span>');h.data("data",i);this.$selection.find(".select2-selection__rendered").prepend(h)};return g});d.define("select2/selection/search",["jquery","../utils","../keys"],function(h,g,e){function f(k,i,j){k.call(this,i,j)}f.prototype.render=function(j){var i=h('<li class="select2-search select2-search--inline"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" aria-autocomplete="list" /></li>');this.$searchContainer=i;this.$search=i.find("input");var k=j.call(this);this._transferTabIndex();return k};f.prototype.bind=function(m,j,n){var k=this;m.call(this,j,n);j.on("open",function(){k.$search.trigger("focus")});j.on("close",function(){k.$search.val("");k.$search.removeAttr("aria-activedescendant");k.$search.trigger("focus")});j.on("enable",function(){k.$search.prop("disabled",false);k._transferTabIndex()});j.on("disable",function(){k.$search.prop("disabled",true)});j.on("focus",function(o){k.$search.trigger("focus")});j.on("results:focus",function(o){k.$search.attr("aria-activedescendant",o.id)});this.$selection.on("focusin",".select2-search--inline",function(o){k.trigger("focus",o)});this.$selection.on("focusout",".select2-search--inline",function(o){k._handleBlur(o)});this.$selection.on("keydown",".select2-search--inline",function(o){o.stopPropagation();k.trigger("keypress",o);k._keyUpPrevented=o.isDefaultPrevented();var q=o.which;if(q===e.BACKSPACE&&k.$search.val()===""){var p=k.$searchContainer.prev(".select2-selection__choice");if(p.length>0){var r=p.data("data");k.searchRemoveChoice(r);o.preventDefault()}}});var l=document.documentMode;var i=l&&l<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(o){if(i){k.$selection.off("input.search input.searchcheck");return}k.$selection.off("keyup.search")});this.$selection.on("keyup.search input.search",".select2-search--inline",function(o){if(i&&o.type==="input"){k.$selection.off("input.search input.searchcheck");return}var p=o.which;if(p==e.SHIFT||p==e.CTRL||p==e.ALT){return}if(p==e.TAB){return}k.handleSearch(o)})};f.prototype._transferTabIndex=function(i){this.$search.attr("tabindex",this.$selection.attr("tabindex"));this.$selection.attr("tabindex","-1")};f.prototype.createPlaceholder=function(i,j){this.$search.attr("placeholder",j.text)};f.prototype.update=function(k,j){var i=this.$search[0]==document.activeElement;this.$search.attr("placeholder","");k.call(this,j);this.$selection.find(".select2-selection__rendered").append(this.$searchContainer);this.resizeSearch();if(i){this.$search.focus()}};f.prototype.handleSearch=function(){this.resizeSearch();if(!this._keyUpPrevented){var i=this.$search.val();this.trigger("query",{term:i})}this._keyUpPrevented=false};f.prototype.searchRemoveChoice=function(j,i){this.trigger("unselect",{data:i});this.$search.val(i.text);this.handleSearch()};f.prototype.resizeSearch=function(){this.$search.css("width","25px");var i="";if(this.$search.attr("placeholder")!==""){i=this.$selection.find(".select2-selection__rendered").innerWidth()}else{var j=this.$search.val().length+1;i=(j*0.75)+"em"}this.$search.css("width",i)};return f});d.define("select2/selection/eventRelay",["jquery"],function(e){function f(){}f.prototype.bind=function(k,g,l){var h=this;var i=["open","opening","close","closing","select","selecting","unselect","unselecting"];var j=["opening","closing","selecting","unselecting"];k.call(this,g,l);g.on("*",function(n,o){if(e.inArray(n,i)===-1){return}o=o||{};var m=e.Event("select2:"+n,{params:o});h.$element.trigger(m);if(e.inArray(n,j)===-1){return}o.prevented=m.isDefaultPrevented()})};return f});d.define("select2/translation",["jquery","require"],function(g,f){function e(h){this.dict=h||{}}e.prototype.all=function(){return this.dict};e.prototype.get=function(h){return this.dict[h]};e.prototype.extend=function(h){this.dict=g.extend({},h.all(),this.dict)};e._cache={};e.loadPath=function(i){if(!(i in e._cache)){var h=f(i);e._cache[i]=h}return new e(e._cache[i])};return e});d.define("select2/diacritics",[],function(){var e={"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038A":"\u0399","\u03AA":"\u0399","\u038C":"\u039F","\u038E":"\u03A5","\u03AB":"\u03A5","\u038F":"\u03A9","\u03AC":"\u03B1","\u03AD":"\u03B5","\u03AE":"\u03B7","\u03AF":"\u03B9","\u03CA":"\u03B9","\u0390":"\u03B9","\u03CC":"\u03BF","\u03CD":"\u03C5","\u03CB":"\u03C5","\u03B0":"\u03C5","\u03C9":"\u03C9","\u03C2":"\u03C3"};
-return e});d.define("select2/data/base",["../utils"],function(f){function e(g,h){e.__super__.constructor.call(this)}f.Extend(e,f.Observable);e.prototype.current=function(g){throw new Error("The `current` method must be defined in child classes.")};e.prototype.query=function(g,h){throw new Error("The `query` method must be defined in child classes.")};e.prototype.bind=function(g,h){};e.prototype.destroy=function(){};e.prototype.generateResultId=function(g,h){var i=g.id+"-result-";i+=f.generateChars(4);if(h.id!=null){i+="-"+h.id.toString()}else{i+="-"+f.generateChars(4)}return i};return e});d.define("select2/data/select",["./base","../utils","jquery"],function(e,h,g){function f(i,j){this.$element=i;this.options=j;f.__super__.constructor.call(this)}h.Extend(f,e);f.prototype.current=function(k){var j=[];var i=this;this.$element.find(":selected").each(function(){var m=g(this);var l=i.item(m);j.push(l)});k(j)};f.prototype.select=function(j){var i=this;j.selected=true;if(g(j.element).is("option")){j.element.selected=true;this.$element.trigger("change");return}if(this.$element.prop("multiple")){this.current(function(l){var n=[];j=[j];j.push.apply(j,l);for(var m=0;m<j.length;m++){var o=j[m].id;if(g.inArray(o,n)===-1){n.push(o)}}i.$element.val(n);i.$element.trigger("change")})}else{var k=j.id;this.$element.val(k);this.$element.trigger("change")}};f.prototype.unselect=function(j){var i=this;if(!this.$element.prop("multiple")){return}j.selected=false;if(g(j.element).is("option")){j.element.selected=false;this.$element.trigger("change");return}this.current(function(k){var m=[];for(var l=0;l<k.length;l++){var n=k[l].id;if(n!==j.id&&g.inArray(n,m)===-1){m.push(n)}}i.$element.val(m);i.$element.trigger("change")})};f.prototype.bind=function(i,k){var j=this;this.container=i;i.on("select",function(l){j.select(l.data)});i.on("unselect",function(l){j.unselect(l.data)})};f.prototype.destroy=function(){this.$element.find("*").each(function(){g.removeData(this,"data")})};f.prototype.query=function(l,m){var k=[];var j=this;var i=this.$element.children();i.each(function(){var p=g(this);if(!p.is("option")&&!p.is("optgroup")){return}var n=j.item(p);var o=j.matches(l,n);if(o!==null){k.push(o)}});m({results:k})};f.prototype.addOptions=function(i){h.appendMany(this.$element,i)};f.prototype.option=function(j){var i;if(j.children){i=document.createElement("optgroup");i.label=j.text}else{i=document.createElement("option");if(i.textContent!==undefined){i.textContent=j.text}else{i.innerText=j.text}}if(j.id){i.value=j.id}if(j.disabled){i.disabled=true}if(j.selected){i.selected=true}if(j.title){i.title=j.title}var k=g(i);var l=this._normalizeItem(j);l.element=i;g.data(i,"data",l);return k};f.prototype.item=function(m){var l={};l=g.data(m[0],"data");if(l!=null){return l}if(m.is("option")){l={id:m.val(),text:m.text(),disabled:m.prop("disabled"),selected:m.prop("selected"),title:m.prop("title")}}else{if(m.is("optgroup")){l={text:m.prop("label"),children:[],title:m.prop("title")};var i=m.children("option");var j=[];for(var o=0;o<i.length;o++){var k=g(i[o]);var n=this.item(k);j.push(n)}l.children=j}}l=this._normalizeItem(l);l.element=m[0];g.data(m[0],"data",l);return l};f.prototype._normalizeItem=function(i){if(!g.isPlainObject(i)){i={id:i,text:i}}i=g.extend({},{text:""},i);var j={selected:false,disabled:false};if(i.id!=null){i.id=i.id.toString()}if(i.text!=null){i.text=i.text.toString()}if(i._resultId==null&&i.id&&this.container!=null){i._resultId=this.generateResultId(this.container,i)}return g.extend({},j,i)};f.prototype.matches=function(k,i){var j=this.options.get("matcher");return j(k,i)};return f});d.define("select2/data/array",["./select","../utils","jquery"],function(e,g,f){function h(i,j){var k=j.get("data")||[];h.__super__.constructor.call(this,i,j);this.addOptions(this.convertToOptions(k))}g.Extend(h,e);h.prototype.select=function(i){var j=this.$element.find("option").filter(function(k,l){return l.value==i.id.toString()});if(j.length===0){j=this.option(i);this.addOptions(j)}h.__super__.select.call(this,i)};h.prototype.convertToOptions=function(k){var u=this;var n=this.$element.find("option");var j=n.map(function(){return u.item(f(this)).id}).get();var m=[];function r(w){return function(){return f(this).val()==w.id}}for(var q=0;q<k.length;q++){var t=this._normalizeItem(k[q]);if(f.inArray(t.id,j)>=0){var o=n.filter(r(t));var p=this.item(o);var v=f.extend(true,{},t,p);var l=this.option(v);o.replaceWith(l);continue}var i=this.option(t);if(t.children){var s=this.convertToOptions(t.children);g.appendMany(i,s)}m.push(i)}return m};return h});d.define("select2/data/ajax",["./array","../utils","jquery"],function(h,g,f){function e(i,j){this.ajaxOptions=this._applyDefaults(j.get("ajax"));if(this.ajaxOptions.processResults!=null){this.processResults=this.ajaxOptions.processResults}e.__super__.constructor.call(this,i,j)}g.Extend(e,h);e.prototype._applyDefaults=function(i){var j={data:function(k){return f.extend({},k,{q:k.term})},transport:function(n,m,l){var k=f.ajax(n);k.then(m);k.fail(l);return k}};return f.extend({},j,i,true)};e.prototype.processResults=function(i){return i};e.prototype.query=function(m,n){var l=[];var i=this;if(this._request!=null){if(f.isFunction(this._request.abort)){this._request.abort()}this._request=null}var j=f.extend({type:"GET"},this.ajaxOptions);if(typeof j.url==="function"){j.url=j.url.call(this.$element,m)}if(typeof j.data==="function"){j.data=j.data.call(this.$element,m)}function k(){var o=j.transport(j,function(q){var p=i.processResults(q,m);if(i.options.get("debug")&&window.console&&console.error){if(!p||!p.results||!f.isArray(p.results)){console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")}}n(p)},function(){if(o.status&&o.status==="0"){return}i.trigger("results:message",{message:"errorLoading"})});i._request=o}if(this.ajaxOptions.delay&&m.term!=null){if(this._queryTimeout){window.clearTimeout(this._queryTimeout)}this._queryTimeout=window.setTimeout(k,this.ajaxOptions.delay)}else{k()}};return e});d.define("select2/data/tags",["jquery"],function(f){function e(h,k,n){var o=n.get("tags");var i=n.get("createTag");if(i!==undefined){this.createTag=i}var j=n.get("insertTag");if(j!==undefined){this.insertTag=j}h.call(this,k,n);if(f.isArray(o)){for(var m=0;m<o.length;m++){var p=o[m];var l=this._normalizeItem(p);var g=this.option(l);this.$element.append(g)}}}e.prototype.query=function(h,i,k){var g=this;this._removeOldTags();if(i.term==null||i.page!=null){h.call(this,i,k);return}function j(p,m){var q=p.results;for(var r=0;r<q.length;r++){var s=q[r];var n=(s.children!=null&&!j({results:s.children},true));var o=s.text===i.term;if(o||n){if(m){return false}p.data=q;k(p);return}}if(m){return true}var t=g.createTag(i);if(t!=null){var l=g.option(t);l.attr("data-select2-tag",true);g.addOptions([l]);g.insertTag(q,t)}p.results=q;k(p)}h.call(this,i,j)};e.prototype.createTag=function(h,i){var g=f.trim(i.term);if(g===""){return null}return{id:g,text:g}};e.prototype.insertTag=function(h,i,g){i.unshift(g)};e.prototype._removeOldTags=function(i){var h=this._lastTag;var g=this.$element.find("option[data-select2-tag]");g.each(function(){if(this.selected){return}f(this).remove()})};return e});d.define("select2/data/tokenizer",["jquery"],function(f){function e(j,g,h){var i=h.get("tokenizer");if(i!==undefined){this.tokenizer=i}j.call(this,g,h)}e.prototype.bind=function(h,g,i){h.call(this,g,i);this.$search=g.dropdown.$search||g.selection.$search||i.find(".select2-search__field")};e.prototype.query=function(j,k,m){var i=this;function h(o){var n=i._normalizeItem(o);var q=i.$element.find("option").filter(function(){return f(this).val()===n.id});if(!q.length){var p=i.option(n);p.attr("data-select2-tag",true);i._removeOldTags();i.addOptions([p])}g(n)}function g(n){i.trigger("select",{data:n})}k.term=k.term||"";var l=this.tokenizer(k,this.options,h);if(l.term!==k.term){if(this.$search.length){this.$search.val(l.term);this.$search.focus()}k.term=l.term}j.call(this,k,m)};e.prototype.tokenizer=function(o,j,s,r){var k=s.get("tokenSeparators")||[];var h=j.term;var m=0;var n=this.createTag||function(i){return{id:i.term,text:i.term}};while(m<h.length){var q=h[m];if(f.inArray(q,k)===-1){m++;continue}var g=h.substr(0,m);var p=f.extend({},j,{term:g});var l=n(p);if(l==null){m++;continue}r(l);h=h.substr(m+1)||"";m=0}return{term:h}};return e});d.define("select2/data/minimumInputLength",[],function(){function e(h,g,f){this.minimumInputLength=f.get("minimumInputLength");h.call(this,g,f)}e.prototype.query=function(f,g,h){g.term=g.term||"";if(g.term.length<this.minimumInputLength){this.trigger("results:message",{message:"inputTooShort",args:{minimum:this.minimumInputLength,input:g.term,params:g}});return}f.call(this,g,h)};return e});d.define("select2/data/maximumInputLength",[],function(){function e(h,g,f){this.maximumInputLength=f.get("maximumInputLength");h.call(this,g,f)}e.prototype.query=function(f,g,h){g.term=g.term||"";if(this.maximumInputLength>0&&g.term.length>this.maximumInputLength){this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:g.term,params:g}});return}f.call(this,g,h)};return e});d.define("select2/data/maximumSelectionLength",[],function(){function e(h,g,f){this.maximumSelectionLength=f.get("maximumSelectionLength");h.call(this,g,f)}e.prototype.query=function(g,h,i){var f=this;this.current(function(j){var k=j!=null?j.length:0;if(f.maximumSelectionLength>0&&k>=f.maximumSelectionLength){f.trigger("results:message",{message:"maximumSelected",args:{maximum:f.maximumSelectionLength}});return}g.call(f,h,i)})};return e});d.define("select2/dropdown",["jquery","./utils"],function(g,f){function e(h,i){this.$element=h;this.options=i;e.__super__.constructor.call(this)}f.Extend(e,f.Observable);e.prototype.render=function(){var h=g('<span class="select2-dropdown"><span class="select2-results"></span></span>');h.attr("dir",this.options.get("dir"));this.$dropdown=h;return h};e.prototype.bind=function(){};e.prototype.position=function(h,i){};e.prototype.destroy=function(){this.$dropdown.remove()};return e});d.define("select2/dropdown/search",["jquery","../utils"],function(g,f){function e(){}e.prototype.render=function(i){var j=i.call(this);var h=g('<span class="select2-search select2-search--dropdown"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" /></span>');this.$searchContainer=h;this.$search=h.find("input");j.prepend(h);return j};e.prototype.bind=function(j,h,k){var i=this;j.call(this,h,k);this.$search.on("keydown",function(l){i.trigger("keypress",l);i._keyUpPrevented=l.isDefaultPrevented()});this.$search.on("input",function(l){g(this).off("keyup")});this.$search.on("keyup input",function(l){i.handleSearch(l)});h.on("open",function(){i.$search.attr("tabindex",0);i.$search.focus();window.setTimeout(function(){i.$search.focus()},0)});h.on("close",function(){i.$search.attr("tabindex",-1);i.$search.val("")});h.on("focus",function(){if(h.isOpen()){i.$search.focus()}});h.on("results:all",function(m){if(m.query.term==null||m.query.term===""){var l=i.showSearch(m);if(l){i.$searchContainer.removeClass("select2-search--hide")}else{i.$searchContainer.addClass("select2-search--hide")}}})};e.prototype.handleSearch=function(h){if(!this._keyUpPrevented){var i=this.$search.val();this.trigger("query",{term:i})}this._keyUpPrevented=false};e.prototype.showSearch=function(h,i){return true};return e});d.define("select2/dropdown/hidePlaceholder",[],function(){function e(h,f,g,i){this.placeholder=this.normalizePlaceholder(g.get("placeholder"));h.call(this,f,g,i)}e.prototype.append=function(g,f){f.results=this.removePlaceholder(f.results);g.call(this,f)};e.prototype.normalizePlaceholder=function(f,g){if(typeof g==="string"){g={id:"",text:g}}return g};e.prototype.removePlaceholder=function(f,i){var h=i.slice(0);for(var j=i.length-1;j>=0;j--){var g=i[j];if(this.placeholder.id===g.id){h.splice(j,1)}}return h};return e});d.define("select2/dropdown/infiniteScroll",["jquery"],function(f){function e(i,g,h,j){this.lastParams={};i.call(this,g,h,j);this.$loadingMore=this.createLoadingMore();this.loading=false}e.prototype.append=function(h,g){this.$loadingMore.remove();this.loading=false;h.call(this,g);if(this.showLoadingMore(g)){this.$results.append(this.$loadingMore)}};e.prototype.bind=function(i,g,j){var h=this;i.call(this,g,j);g.on("query",function(k){h.lastParams=k;h.loading=true});g.on("query:append",function(k){h.lastParams=k;h.loading=true});this.$results.on("scroll",function(){var m=f.contains(document.documentElement,h.$loadingMore[0]);if(h.loading||!m){return}var l=h.$results.offset().top+h.$results.outerHeight(false);var k=h.$loadingMore.offset().top+h.$loadingMore.outerHeight(false);if(l+50>=k){h.loadMore()}})};e.prototype.loadMore=function(){this.loading=true;var g=f.extend({},{page:1},this.lastParams);g.page++;this.trigger("query:append",g)};e.prototype.showLoadingMore=function(g,h){return h.pagination&&h.pagination.more};e.prototype.createLoadingMore=function(){var h=f('<li class="select2-results__option select2-results__option--load-more"role="treeitem" aria-disabled="true"></li>');var g=this.options.get("translations").get("loadingMore");h.html(g(this.lastParams));return h};return e});d.define("select2/dropdown/attachBody",["jquery","../utils"],function(g,f){function e(j,h,i){this.$dropdownParent=i.get("dropdownParent")||g(document.body);j.call(this,h,i)}e.prototype.bind=function(k,h,l){var j=this;var i=false;k.call(this,h,l);h.on("open",function(){j._showDropdown();j._attachPositioningHandler(h);if(!i){i=true;h.on("results:all",function(){j._positionDropdown();j._resizeDropdown()});h.on("results:append",function(){j._positionDropdown();j._resizeDropdown()})}});h.on("close",function(){j._hideDropdown();j._detachPositioningHandler(h)});this.$dropdownContainer.on("mousedown",function(m){m.stopPropagation()})};e.prototype.destroy=function(h){h.call(this);this.$dropdownContainer.remove()};e.prototype.position=function(h,i,j){i.attr("class",j.attr("class"));i.removeClass("select2");i.addClass("select2-container--open");i.css({position:"absolute",top:-999999});this.$container=j};e.prototype.render=function(h){var j=g("<span></span>");var i=h.call(this);j.append(i);this.$dropdownContainer=j;return j};e.prototype._hideDropdown=function(h){this.$dropdownContainer.detach()};e.prototype._attachPositioningHandler=function(n,h){var i=this;var k="scroll.select2."+h.id;var m="resize.select2."+h.id;var l="orientationchange.select2."+h.id;var j=this.$container.parents().filter(f.hasScroll);j.each(function(){g(this).data("select2-scroll-position",{x:g(this).scrollLeft(),y:g(this).scrollTop()})});j.on(k,function(p){var o=g(this).data("select2-scroll-position");g(this).scrollTop(o.y)});g(window).on(k+" "+m+" "+l,function(o){i._positionDropdown();i._resizeDropdown()})};e.prototype._detachPositioningHandler=function(m,h){var j="scroll.select2."+h.id;var l="resize.select2."+h.id;var k="orientationchange.select2."+h.id;var i=this.$container.parents().filter(f.hasScroll);i.off(j);g(window).off(j+" "+l+" "+k)};e.prototype._positionDropdown=function(){var k=g(window);var t=this.$dropdown.hasClass("select2-dropdown--above");var j=this.$dropdown.hasClass("select2-dropdown--below");var q=null;var l=this.$container.offset();l.bottom=l.top+this.$container.outerHeight(false);var i={height:this.$container.outerHeight(false)};i.top=l.top;i.bottom=l.top+i.height;var s={height:this.$dropdown.outerHeight(false)};var o={top:k.scrollTop(),bottom:k.scrollTop()+k.height()};var n=o.top<(l.top-s.height);var p=o.bottom>(l.bottom+s.height);var m={left:l.left,top:i.bottom};var r=this.$dropdownParent;if(r.css("position")==="static"){r=r.offsetParent()}var h=r.offset();m.top-=h.top;m.left-=h.left;if(!t&&!j){q="below"}if(!p&&n&&!t){q="above"}else{if(!n&&p&&t){q="below"}}if(q=="above"||(t&&q!=="below")){m.top=i.top-h.top-s.height}if(q!=null){this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+q);this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+q)}this.$dropdownContainer.css(m)};e.prototype._resizeDropdown=function(){var h={width:this.$container.outerWidth(false)+"px"};if(this.options.get("dropdownAutoWidth")){h.minWidth=h.width;h.position="relative";h.width="auto"}this.$dropdown.css(h)};e.prototype._showDropdown=function(h){this.$dropdownContainer.appendTo(this.$dropdownParent);this._positionDropdown();this._resizeDropdown()};return e});d.define("select2/dropdown/minimumResultsForSearch",[],function(){function f(i){var h=0;for(var j=0;j<i.length;j++){var g=i[j];if(g.children){h+=f(g.children)}else{h++}}return h}function e(i,g,h,j){this.minimumResultsForSearch=h.get("minimumResultsForSearch");if(this.minimumResultsForSearch<0){this.minimumResultsForSearch=Infinity}i.call(this,g,h,j)}e.prototype.showSearch=function(g,h){if(f(h.data.results)<this.minimumResultsForSearch){return false}return g.call(this,h)};return e});d.define("select2/dropdown/selectOnClose",[],function(){function e(){}e.prototype.bind=function(h,f,i){var g=this;h.call(this,f,i);f.on("close",function(j){g._handleSelectOnClose(j)})};e.prototype._handleSelectOnClose=function(g,j){if(j&&j.originalSelect2Event!=null){var h=j.originalSelect2Event;if(h._type==="select"||h._type==="unselect"){return}}var f=this.getHighlightedResults();if(f.length<1){return}var i=f.data("data");if((i.element!=null&&i.element.selected)||(i.element==null&&i.selected)){return}this.trigger("select",{data:i})};return e});d.define("select2/dropdown/closeOnSelect",[],function(){function e(){}e.prototype.bind=function(h,f,i){var g=this;h.call(this,f,i);f.on("select",function(j){g._selectTriggered(j)});f.on("unselect",function(j){g._selectTriggered(j)})};e.prototype._selectTriggered=function(h,g){var f=g.originalEvent;if(f&&f.ctrlKey){return}this.trigger("close",{originalEvent:f,originalSelect2Event:g})};return e});d.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(e){var g=e.input.length-e.maximum;var f="请删除 "+g+" 个字符";if(g!=1){f+="s"}return f},inputTooShort:function(e){var g=e.minimum-e.input.length;var f="请再输入至少 "+g+" 个字符";return f},loadingMore:function(){return"加载更多..."},maximumSelected:function(e){var f="最多只能选择 "+e.maximum+" 个项目";if(e.maximum!=1){f+="s"}return f},noResults:function(){return"没有找到匹配项"},searching:function(){return"正在搜索..."}}});d.define("select2/defaults",["jquery","require","./results","./selection/single","./selection/multiple","./selection/placeholder","./selection/allowClear","./selection/search","./selection/eventRelay","./utils","./translation","./diacritics","./data/select","./data/array","./data/ajax","./data/tags","./data/tokenizer","./data/minimumInputLength","./data/maximumInputLength","./data/maximumSelectionLength","./dropdown","./dropdown/search","./dropdown/hidePlaceholder","./dropdown/infiniteScroll","./dropdown/attachBody","./dropdown/minimumResultsForSearch","./dropdown/selectOnClose","./dropdown/closeOnSelect","./i18n/en"],function(g,k,E,o,t,G,j,C,F,e,u,I,B,s,f,x,h,D,H,w,A,m,q,z,r,v,l,i,y){function p(){this.reset()}p.prototype.apply=function(Z){Z=g.extend(true,{},this.defaults,Z);if(Z.dataAdapter==null){if(Z.ajax!=null){Z.dataAdapter=f}else{if(Z.data!=null){Z.dataAdapter=s}else{Z.dataAdapter=B}}if(Z.minimumInputLength>0){Z.dataAdapter=e.Decorate(Z.dataAdapter,D)}if(Z.maximumInputLength>0){Z.dataAdapter=e.Decorate(Z.dataAdapter,H)}if(Z.maximumSelectionLength>0){Z.dataAdapter=e.Decorate(Z.dataAdapter,w)}if(Z.tags){Z.dataAdapter=e.Decorate(Z.dataAdapter,x)}if(Z.tokenSeparators!=null||Z.tokenizer!=null){Z.dataAdapter=e.Decorate(Z.dataAdapter,h)}if(Z.query!=null){var R=k(Z.amdBase+"compat/query");Z.dataAdapter=e.Decorate(Z.dataAdapter,R)}if(Z.initSelection!=null){var Y=k(Z.amdBase+"compat/initSelection");Z.dataAdapter=e.Decorate(Z.dataAdapter,Y)}}if(Z.resultsAdapter==null){Z.resultsAdapter=E;if(Z.ajax!=null){Z.resultsAdapter=e.Decorate(Z.resultsAdapter,z)}if(Z.placeholder!=null){Z.resultsAdapter=e.Decorate(Z.resultsAdapter,q)}if(Z.selectOnClose){Z.resultsAdapter=e.Decorate(Z.resultsAdapter,l)}}if(Z.dropdownAdapter==null){if(Z.multiple){Z.dropdownAdapter=A}else{var L=e.Decorate(A,m);Z.dropdownAdapter=L}if(Z.minimumResultsForSearch!==0){Z.dropdownAdapter=e.Decorate(Z.dropdownAdapter,v)}if(Z.closeOnSelect){Z.dropdownAdapter=e.Decorate(Z.dropdownAdapter,i)}if(Z.dropdownCssClass!=null||Z.dropdownCss!=null||Z.adaptDropdownCssClass!=null){var K=k(Z.amdBase+"compat/dropdownCss");Z.dropdownAdapter=e.Decorate(Z.dropdownAdapter,K)}Z.dropdownAdapter=e.Decorate(Z.dropdownAdapter,r)}if(Z.selectionAdapter==null){if(Z.multiple){Z.selectionAdapter=t}else{Z.selectionAdapter=o}if(Z.placeholder!=null){Z.selectionAdapter=e.Decorate(Z.selectionAdapter,G)}if(Z.allowClear){Z.selectionAdapter=e.Decorate(Z.selectionAdapter,j)}if(Z.multiple){Z.selectionAdapter=e.Decorate(Z.selectionAdapter,C)}if(Z.containerCssClass!=null||Z.containerCss!=null||Z.adaptContainerCssClass!=null){var T=k(Z.amdBase+"compat/containerCss");Z.selectionAdapter=e.Decorate(Z.selectionAdapter,T)}Z.selectionAdapter=e.Decorate(Z.selectionAdapter,F)}if(typeof Z.language==="string"){if(Z.language.indexOf("-")>0){var O=Z.language.split("-");var Q=O[0];Z.language=[Z.language,Q]}else{Z.language=[Z.language]}}if(g.isArray(Z.language)){var N=new u();Z.language.push("en");var W=Z.language;for(var M=0;M<W.length;M++){var J=W[M];var P={};try{P=u.loadPath(J)}catch(S){try{J=this.defaults.amdLanguageBase+J;P=u.loadPath(J)}catch(V){if(Z.debug&&window.console&&console.warn){console.warn('Select2: The language file for "'+J+'" could not be automatically loaded. A fallback will be used instead.')}continue}}N.extend(P)}Z.translations=N}else{var U=u.loadPath(this.defaults.amdLanguageBase+"en");var X=new u(Z.language);X.extend(U);Z.translations=X}return Z};p.prototype.reset=function(){function K(M){function L(N){return I[N]||N}return M.replace(/[^\u0000-\u007E]/g,L)}function J(Q,P){if(g.trim(Q.term)===""){return P}if(P.children&&P.children.length>0){var L=g.extend(true,{},P);for(var S=P.children.length-1;S>=0;S--){var R=P.children[S];var O=J(Q,R);if(O==null){L.children.splice(S,1)}}if(L.children.length>0){return L}return J(Q,L)}var N=K(P.text).toUpperCase();var M=K(Q.term).toUpperCase();if(N.indexOf(M)>-1){return P}return null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:true,debug:false,dropdownAutoWidth:false,escapeMarkup:e.escapeMarkup,language:y,matcher:J,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:false,sorter:function(L){return L},templateResult:function(L){return L.text},templateSelection:function(L){return L.text},theme:"default",width:"100%"}};p.prototype.set=function(K,M){var J=g.camelCase(K);var L={};L[J]=M;var N=e._convertData(L);g.extend(this.defaults,N)};var n=new p();return n});d.define("select2/options",["require","jquery","./defaults","./utils"],function(f,h,i,g){function e(l,j){this.options=l;if(j!=null){this.fromElement(j)}this.options=i.apply(this.options);if(j&&j.is("input")){var k=f(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=g.Decorate(this.options.dataAdapter,k)}}e.prototype.fromElement=function(j){var l=["select2"];if(this.options.multiple==null){this.options.multiple=j.prop("multiple")}if(this.options.disabled==null){this.options.disabled=j.prop("disabled")}if(this.options.language==null){if(j.prop("lang")){this.options.language=j.prop("lang").toLowerCase()}else{if(j.closest("[lang]").prop("lang")){this.options.language=j.closest("[lang]").prop("lang")}}}if(this.options.dir==null){if(j.prop("dir")){this.options.dir=j.prop("dir")}else{if(j.closest("[dir]").prop("dir")){this.options.dir=j.closest("[dir]").prop("dir")}else{this.options.dir="ltr"}}}j.prop("disabled",this.options.disabled);j.prop("multiple",this.options.multiple);if(j.data("select2Tags")){if(this.options.debug&&window.console&&console.warn){console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.')}j.data("data",j.data("select2Tags"));j.data("tags",true)}if(j.data("ajaxUrl")){if(this.options.debug&&window.console&&console.warn){console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2.")}j.attr("ajax--url",j.data("ajaxUrl"));j.data("ajax--url",j.data("ajaxUrl"))}var n={};if(h.fn.jquery&&h.fn.jquery.substr(0,2)=="1."&&j[0].dataset){n=h.extend(true,{},j[0].dataset,j.data())}else{n=j.data()}var m=h.extend(true,{},n);m=g._convertData(m);for(var k in m){if(h.inArray(k,l)>-1){continue}if(h.isPlainObject(this.options[k])){h.extend(this.options[k],m[k])}else{this.options[k]=m[k]}}return this};e.prototype.get=function(j){return this.options[j]};e.prototype.set=function(j,k){this.options[j]=k};return e});d.define("select2/core",["jquery","./options","./utils","./keys"],function(i,g,h,e){var f=function(o,r){if(o.data("select2")!=null){o.data("select2").destroy()}this.$element=o;this.id=this._generateId(o);r=r||{};this.options=new g(r,o);f.__super__.constructor.call(this);var m=o.attr("tabindex")||0;o.data("old-tabindex",m);o.attr("tabindex","-1");var l=this.options.get("dataAdapter");this.dataAdapter=new l(o,this.options);var q=this.render();this._placeContainer(q);var n=this.options.get("selectionAdapter");this.selection=new n(o,this.options);this.$selection=this.selection.render();this.selection.position(this.$selection,q);var j=this.options.get("dropdownAdapter");this.dropdown=new j(o,this.options);this.$dropdown=this.dropdown.render();this.dropdown.position(this.$dropdown,q);var k=this.options.get("resultsAdapter");this.results=new k(o,this.options,this.dataAdapter);this.$results=this.results.render();this.results.position(this.$results,this.$dropdown);var p=this;this._bindAdapters();this._registerDomEvents();this._registerDataEvents();this._registerSelectionEvents();this._registerDropdownEvents();this._registerResultsEvents();this._registerEvents();this.dataAdapter.current(function(s){p.trigger("selection:update",{data:s})});o.addClass("select2-hidden-accessible");o.attr("aria-hidden","true");this._syncAttributes();o.data("select2",this)};h.Extend(f,h.Observable);f.prototype._generateId=function(j){var k="";if(j.attr("id")!=null){k=j.attr("id")}else{if(j.attr("name")!=null){k=j.attr("name")+"-"+h.generateChars(2)}else{k=h.generateChars(4)}}k=k.replace(/(:|\.|\[|\]|,)/g,"");k="select2-"+k;return k};f.prototype._placeContainer=function(k){k.insertAfter(this.$element);var j=this._resolveWidth(this.$element,this.options.get("width"));if(j!=null){k.css("width",j)}};f.prototype._resolveWidth=function(u,j){var s=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if(j=="resolve"){var n=this._resolveWidth(u,"style");if(n!=null){return n}return this._resolveWidth(u,"element")}if(j=="element"){var r=u.outerWidth(false);if(r<=0){return"auto"}return r+"px"}if(j=="style"){var k=u.attr("style");if(typeof(k)!=="string"){return null}var t=k.split(";");for(var o=0,m=t.length;o<m;o=o+1){var q=t[o].replace(/\s/g,"");var p=q.match(s);if(p!==null&&p.length>=1){return p[1]}}return null}return j};f.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container);this.selection.bind(this,this.$container);this.dropdown.bind(this,this.$container);this.results.bind(this,this.$container)};f.prototype._registerDomEvents=function(){var k=this;this.$element.on("change.select2",function(){k.dataAdapter.current(function(l){k.trigger("selection:update",{data:l})})});this.$element.on("focus.select2",function(l){k.trigger("focus",l)});this._syncA=h.bind(this._syncAttributes,this);this._syncS=h.bind(this._syncSubtree,this);if(this.$element[0].attachEvent){this.$element[0].attachEvent("onpropertychange",this._syncA)}var j=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;if(j!=null){this._observer=new j(function(l){i.each(l,k._syncA);i.each(l,k._syncS)});this._observer.observe(this.$element[0],{attributes:true,childList:true,subtree:false})}else{if(this.$element[0].addEventListener){this.$element[0].addEventListener("DOMAttrModified",k._syncA,false);this.$element[0].addEventListener("DOMNodeInserted",k._syncS,false);this.$element[0].addEventListener("DOMNodeRemoved",k._syncS,false)}}};f.prototype._registerDataEvents=function(){var j=this;this.dataAdapter.on("*",function(k,l){j.trigger(k,l)})};f.prototype._registerSelectionEvents=function(){var j=this;var k=["toggle","focus"];this.selection.on("toggle",function(){j.toggleDropdown()});this.selection.on("focus",function(l){j.focus(l)});this.selection.on("*",function(l,m){if(i.inArray(l,k)!==-1){return}j.trigger(l,m)})};f.prototype._registerDropdownEvents=function(){var j=this;this.dropdown.on("*",function(k,l){j.trigger(k,l)})};f.prototype._registerResultsEvents=function(){var j=this;this.results.on("*",function(k,l){j.trigger(k,l)})};f.prototype._registerEvents=function(){var j=this;this.on("open",function(){j.$container.addClass("select2-container--open")});this.on("close",function(){j.$container.removeClass("select2-container--open");j.$selection.focus()});this.on("enable",function(){j.$container.removeClass("select2-container--disabled")});this.on("disable",function(){j.$container.addClass("select2-container--disabled")});this.on("blur",function(){j.$container.removeClass("select2-container--focus")});this.on("query",function(k){if(!j.isOpen()){j.trigger("open",{})}this.dataAdapter.query(k,function(l){j.trigger("results:all",{data:l,query:k})})});this.on("query:append",function(k){this.dataAdapter.query(k,function(l){j.trigger("results:append",{data:l,query:k})})});this.on("keypress",function(k){var l=k.which;
-if(j.isOpen()){if(l===e.ESC||l===e.TAB||(l===e.UP&&k.altKey)){j.close();k.preventDefault()}else{if(l===e.ENTER){j.trigger("results:select",{});k.preventDefault()}else{if((l===e.SPACE&&k.ctrlKey)){j.trigger("results:toggle",{});k.preventDefault()}else{if(l===e.UP){j.trigger("results:previous",{});k.preventDefault()}else{if(l===e.DOWN){j.trigger("results:next",{});k.preventDefault()}}}}}}else{if(l===e.SPACE||(l===e.DOWN&&k.altKey)){j.open();k.preventDefault()}}})};f.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled"));if(this.options.get("disabled")){if(this.isOpen()){this.close()}this.trigger("disable",{})}else{this.trigger("enable",{})}};f.prototype._syncSubtree=function(k,j){var o=false;var l=this;if(k&&k.target&&(k.target.nodeName!=="OPTION"&&k.target.nodeName!=="OPTGROUP")){return}if(!j){o=true}else{if(j.addedNodes&&j.addedNodes.length>0){for(var p=0;p<j.addedNodes.length;p++){var m=j.addedNodes[p];if(m.selected){o=true}}}else{if(j.removedNodes&&j.removedNodes.length>0){o=true}}}if(o){this.dataAdapter.current(function(n){l.trigger("selection:update",{data:n})})}};f.prototype.trigger=function(m,l){var n=f.__super__.trigger;var o={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(l===undefined){l={}}if(m in o){var k=o[m];var j={prevented:false,name:m,args:l};n.call(this,k,j);if(j.prevented){l.prevented=true;return}}n.call(this,m,l)};f.prototype.toggleDropdown=function(){if(this.options.get("disabled")){return}if(this.isOpen()){this.close()}else{this.open()}};f.prototype.open=function(){if(this.isOpen()){return}this.trigger("query",{})};f.prototype.close=function(){if(!this.isOpen()){return}this.trigger("close",{})};f.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")};f.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")};f.prototype.focus=function(j){if(this.hasFocus()){return}this.$container.addClass("select2-container--focus");this.trigger("focus",{})};f.prototype.enable=function(j){if(this.options.get("debug")&&window.console&&console.warn){console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.')}if(j==null||j.length===0){j=[true]}var k=!j[0];this.$element.prop("disabled",k)};f.prototype.data=function(){if(this.options.get("debug")&&arguments.length>0&&window.console&&console.warn){console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.')}var j=[];this.dataAdapter.current(function(k){j=k});return j};f.prototype.val=function(k){if(this.options.get("debug")&&window.console&&console.warn){console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.')}if(k==null||k.length===0){return this.$element.val()}var j=k[0];if(i.isArray(j)){j=i.map(j,function(l){return l.toString()})}this.$element.val(j).trigger("change")};f.prototype.destroy=function(){this.$container.remove();if(this.$element[0].detachEvent){this.$element[0].detachEvent("onpropertychange",this._syncA)}if(this._observer!=null){this._observer.disconnect();this._observer=null}else{if(this.$element[0].removeEventListener){this.$element[0].removeEventListener("DOMAttrModified",this._syncA,false);this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,false);this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,false)}}this._syncA=null;this._syncS=null;this.$element.off(".select2");this.$element.attr("tabindex",this.$element.data("old-tabindex"));this.$element.removeClass("select2-hidden-accessible");this.$element.attr("aria-hidden","false");this.$element.removeData("select2");this.dataAdapter.destroy();this.selection.destroy();this.dropdown.destroy();this.results.destroy();this.dataAdapter=null;this.selection=null;this.dropdown=null;this.results=null};f.prototype.render=function(){var j=i('<span class="select2 select2-container"><span class="selection"></span><span class="dropdown-wrapper" aria-hidden="true"></span></span>');j.attr("dir",this.options.get("dir"));this.$container=j;this.$container.addClass("select2-container--"+this.options.get("theme"));j.data("element",this.$element);return j};return f});d.define("jquery-mousewheel",["jquery"],function(e){return e});d.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults"],function(h,g,e,i){if(h.fn.select2==null){var f=["open","close","destroy"];h.fn.select2=function(l){l=l||{};if(typeof l==="object"){this.each(function(){var n=h.extend(true,{},l);var m=new e(h(this),n)});return this}else{if(typeof l==="string"){var k;var j=Array.prototype.slice.call(arguments,1);this.each(function(){var m=h(this).data("select2");if(m==null&&window.console&&console.error){console.error("The select2('"+l+"') method was called on an element that is not using Select2.")}k=m[l].apply(m,j)});if(h.inArray(l,f)>-1){return this}return k}else{throw new Error("Invalid arguments for Select2: "+l)}}}}if(h.fn.select2.defaults==null){h.fn.select2.defaults=i}return e});return{define:d.define,require:d.require}}());var a=b.require("jquery.select2");c.fn.select2.amd=b;return a}));
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/select2/select2-bootstrap.css b/src/main/resources/static/ajax/libs/select2/select2-bootstrap.css
new file mode 100644
index 0000000..ed7de96
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/select2/select2-bootstrap.css
@@ -0,0 +1,7 @@
+/*!
+ * Select2 Bootstrap Theme v0.1.0-beta.10 (https://select2.github.io/select2-bootstrap-theme)
+ * Copyright 2015-2017 Florian Kissling and contributors (https://github.com/select2/select2-bootstrap-theme/graphs/contributors)
+ * Licensed under MIT (https://github.com/select2/select2-bootstrap-theme/blob/master/LICENSE)
+ */
+
+.select2-container--bootstrap{display:block}.select2-container--bootstrap .select2-selection{background-color:#fff;border:1px solid #e5e6e7;border-radius:1px;color:#555;font-size:14px;outline:0}.select2-container--bootstrap .select2-selection.form-control{border-radius:4px}.select2-container--bootstrap .select2-search--dropdown .select2-search__field{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);background-color:#fff;border:1px solid #ccc;border-radius:4px;color:#555;font-size:14px}.select2-container--bootstrap .select2-search__field{outline:0}.select2-container--bootstrap .select2-search__field::-webkit-input-placeholder{color:#999}.select2-container--bootstrap .select2-search__field:-moz-placeholder{color:#999}.select2-container--bootstrap .select2-search__field::-moz-placeholder{color:#999;opacity:1}.select2-container--bootstrap .select2-search__field:-ms-input-placeholder{color:#999}.select2-container--bootstrap .select2-results__option{padding:6px 12px}.select2-container--bootstrap .select2-results__option[role=group]{padding:0}.select2-container--bootstrap .select2-results__option[aria-disabled=true]{color:#777;cursor:not-allowed}.select2-container--bootstrap .select2-results__option[aria-selected=true]{background-color:#f5f5f5;color:#262626}.select2-container--bootstrap .select2-results__option--highlighted[aria-selected]{background-color:#337ab7;color:#fff}.select2-container--bootstrap .select2-results__option .select2-results__option{padding:6px 12px}.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option{margin-left:-12px;padding-left:24px}.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-24px;padding-left:36px}.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-36px;padding-left:48px}.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-48px;padding-left:60px}.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-60px;padding-left:72px}.select2-container--bootstrap .select2-results__group{color:#777;display:block;padding:6px 12px;font-size:12px;line-height:1.42857143;white-space:nowrap}.select2-container--bootstrap.select2-container--focus .select2-selection,.select2-container--bootstrap.select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;border-color:#66afe9}.select2-container--bootstrap.select2-container--open .select2-selection .select2-selection__arrow b{border-color:transparent transparent #999;border-width:0 4px 4px}.select2-container--bootstrap.select2-container--open.select2-container--below .select2-selection{border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom-color:transparent}.select2-container--bootstrap.select2-container--open.select2-container--above .select2-selection{border-top-right-radius:0;border-top-left-radius:0;border-top-color:transparent}.select2-container--bootstrap .select2-selection__clear{color:#999;cursor:pointer;float:right;font-weight:700;margin-right:10px}.select2-container--bootstrap .select2-selection__clear:hover{color:#333}.select2-container--bootstrap.select2-container--disabled .select2-selection{border-color:#ccc;-webkit-box-shadow:none;box-shadow:none}.select2-container--bootstrap.select2-container--disabled .select2-search__field,.select2-container--bootstrap.select2-container--disabled .select2-selection{cursor:not-allowed}.select2-container--bootstrap.select2-container--disabled .select2-selection,.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice{background-color:#eee}.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice__remove,.select2-container--bootstrap.select2-container--disabled .select2-selection__clear{display:none}.select2-container--bootstrap .select2-dropdown{-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);border-color:#66afe9;overflow-x:hidden;margin-top:-1px}.select2-container--bootstrap .select2-dropdown--above{-webkit-box-shadow:0 -6px 12px rgba(0,0,0,.175);box-shadow:0 -6px 12px rgba(0,0,0,.175);margin-top:1px}.select2-container--bootstrap .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--bootstrap .select2-selection--single{height:34px;line-height:1.42857143;padding:6px 24px 6px 12px}.select2-container--bootstrap .select2-selection--single .select2-selection__arrow{position:absolute;bottom:0;right:12px;top:0;width:4px}.select2-container--bootstrap .select2-selection--single .select2-selection__arrow b{border-color:#999 transparent transparent;border-style:solid;border-width:4px 4px 0;height:0;left:0;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--bootstrap .select2-selection--single .select2-selection__rendered{color:#555;padding:0}.select2-container--bootstrap .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--bootstrap .select2-selection--multiple{min-height:34px;padding:0;height:auto}.select2-container--bootstrap .select2-selection--multiple .select2-selection__rendered{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;line-height:1.42857143;list-style:none;margin:0;overflow:hidden;padding:0;width:100%;text-overflow:ellipsis;white-space:nowrap}.select2-container--bootstrap .select2-selection--multiple .select2-selection__placeholder{color:#999;float:left;margin-top:5px}.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice{color:#fff;background:#1AB394;border:1px solid #1AB394;border-radius:4px;cursor:default;float:left;margin:5px 0 0 6px;padding:0 6px}.select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field{background:0 0;padding:0 12px;height:32px;line-height:1.42857143;margin-top:0;min-width:5em}.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove{color:#fff;cursor:pointer;display:inline-block;font-weight:700;margin-right:3px}.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--bootstrap .select2-selection--multiple .select2-selection__clear{margin-top:6px}.form-group-sm .select2-container--bootstrap .select2-selection--single,.input-group-sm .select2-container--bootstrap .select2-selection--single,.select2-container--bootstrap .select2-selection--single.input-sm{border-radius:3px;font-size:12px;height:30px;line-height:1.5;padding:5px 22px 5px 10px}.form-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,.input-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,.select2-container--bootstrap .select2-selection--single.input-sm .select2-selection__arrow b{margin-left:-5px}.form-group-sm .select2-container--bootstrap .select2-selection--multiple,.input-group-sm .select2-container--bootstrap .select2-selection--multiple,.select2-container--bootstrap .select2-selection--multiple.input-sm{min-height:30px;border-radius:3px}.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__choice{font-size:12px;line-height:1.5;margin:4px 0 0 5px;padding:0 5px}.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-search--inline .select2-search__field{padding:0 10px;font-size:12px;height:28px;line-height:1.5}.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__clear{margin-top:5px}.form-group-lg .select2-container--bootstrap .select2-selection--single,.input-group-lg .select2-container--bootstrap .select2-selection--single,.select2-container--bootstrap .select2-selection--single.input-lg{border-radius:6px;font-size:18px;height:46px;line-height:1.3333333;padding:10px 31px 10px 16px}.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow,.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow,.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow{width:5px}.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow b{border-width:5px 5px 0;margin-left:-10px;margin-top:-2.5px}.form-group-lg .select2-container--bootstrap .select2-selection--multiple,.input-group-lg .select2-container--bootstrap .select2-selection--multiple,.select2-container--bootstrap .select2-selection--multiple.input-lg{min-height:46px;border-radius:6px}.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__choice{font-size:18px;line-height:1.3333333;border-radius:4px;margin:9px 0 0 8px;padding:0 10px}.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-search--inline .select2-search__field{padding:0 16px;font-size:18px;height:44px;line-height:1.3333333}.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__clear{margin-top:10px}.input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single .select2-selection__arrow b,.select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #999;border-width:0 5px 5px}.select2-container--bootstrap[dir=rtl] .select2-selection--single{padding-left:24px;padding-right:12px}.select2-container--bootstrap[dir=rtl] .select2-selection--single .select2-selection__rendered{padding-right:0;padding-left:0;text-align:right}.select2-container--bootstrap[dir=rtl] .select2-selection--single .select2-selection__clear{float:left}.select2-container--bootstrap[dir=rtl] .select2-selection--single .select2-selection__arrow{left:12px;right:auto}.select2-container--bootstrap[dir=rtl] .select2-selection--single .select2-selection__arrow b{margin-left:0}.select2-container--bootstrap[dir=rtl] .select2-selection--multiple .select2-search--inline,.select2-container--bootstrap[dir=rtl] .select2-selection--multiple .select2-selection__choice,.select2-container--bootstrap[dir=rtl] .select2-selection--multiple .select2-selection__placeholder{float:right}.select2-container--bootstrap[dir=rtl] .select2-selection--multiple .select2-selection__choice{margin-left:0;margin-right:6px}.select2-container--bootstrap[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.has-warning .select2-dropdown,.has-warning .select2-selection{border-color:#8a6d3b}.has-warning .select2-container--focus .select2-selection,.has-warning .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;border-color:#66512c}.has-warning.select2-drop-active{border-color:#66512c}.has-warning.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#66512c}.has-error .select2-dropdown,.has-error .select2-selection{border-color:#a94442}.has-error .select2-container--focus .select2-selection,.has-error .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;border-color:#843534}.has-error.select2-drop-active{border-color:#843534}.has-error.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#843534}.has-success .select2-dropdown,.has-success .select2-selection{border-color:#3c763d}.has-success .select2-container--focus .select2-selection,.has-success .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;border-color:#2b542c}.has-success.select2-drop-active{border-color:#2b542c}.has-success.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#2b542c}.input-group>.select2-hidden-accessible:first-child+.select2-container--bootstrap>.selection>.select2-selection,.input-group>.select2-hidden-accessible:first-child+.select2-container--bootstrap>.selection>.select2-selection.form-control{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.select2-hidden-accessible:not(:first-child)+.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,.input-group>.select2-hidden-accessible:not(:first-child)+.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection.form-control{border-radius:0}.input-group>.select2-hidden-accessible:not(:first-child):not(:last-child)+.select2-container--bootstrap:last-child>.selection>.select2-selection,.input-group>.select2-hidden-accessible:not(:first-child):not(:last-child)+.select2-container--bootstrap:last-child>.selection>.select2-selection.form-control{border-bottom-left-radius:0;border-top-left-radius:0}.input-group>.select2-container--bootstrap{display:table;table-layout:fixed;position:relative;z-index:2;width:100%;margin-bottom:0}.input-group>.select2-container--bootstrap>.selection>.select2-selection.form-control{float:none}.input-group>.select2-container--bootstrap.select2-container--focus,.input-group>.select2-container--bootstrap.select2-container--open{z-index:3}.input-group>.select2-container--bootstrap,.input-group>.select2-container--bootstrap .input-group-btn,.input-group>.select2-container--bootstrap .input-group-btn .btn{vertical-align:top}.form-control.select2-hidden-accessible{position:absolute!important;width:1px!important}@media (min-width:768px){.form-inline .select2-container--bootstrap{display:inline-block}}
diff --git a/src/main/resources/static/ajax/libs/select2/select2.css b/src/main/resources/static/ajax/libs/select2/select2.css
new file mode 100644
index 0000000..ce3afd1
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/select2/select2.css
@@ -0,0 +1,484 @@
+.select2-container {
+  box-sizing: border-box;
+  display: inline-block;
+  margin: 0;
+  position: relative;
+  vertical-align: middle; }
+  .select2-container .select2-selection--single {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    height: 28px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--single .select2-selection__rendered {
+      display: block;
+      padding-left: 8px;
+      padding-right: 20px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+    .select2-container .select2-selection--single .select2-selection__clear {
+      position: relative; }
+  .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
+    padding-right: 8px;
+    padding-left: 20px; }
+  .select2-container .select2-selection--multiple {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    min-height: 32px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--multiple .select2-selection__rendered {
+      display: inline-block;
+      overflow: hidden;
+      padding-left: 8px;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+  .select2-container .select2-search--inline {
+    float: left; }
+    .select2-container .select2-search--inline .select2-search__field {
+      box-sizing: border-box;
+      border: none;
+      font-size: 100%;
+      margin-top: 5px;
+      padding: 0; }
+      .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
+        -webkit-appearance: none; }
+
+.select2-dropdown {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  box-sizing: border-box;
+  display: block;
+  position: absolute;
+  left: -100000px;
+  width: 100%;
+  z-index: 1051; }
+
+.select2-results {
+  display: block; }
+
+.select2-results__options {
+  list-style: none;
+  margin: 0;
+  padding: 0; }
+
+.select2-results__option {
+  padding: 6px;
+  user-select: none;
+  -webkit-user-select: none; }
+  .select2-results__option[aria-selected] {
+    cursor: pointer; }
+
+.select2-container--open .select2-dropdown {
+  left: 0; }
+
+.select2-container--open .select2-dropdown--above {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--open .select2-dropdown--below {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-search--dropdown {
+  display: block;
+  padding: 4px; }
+  .select2-search--dropdown .select2-search__field {
+    padding: 4px;
+    width: 100%;
+    box-sizing: border-box; }
+    .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
+      -webkit-appearance: none; }
+  .select2-search--dropdown.select2-search--hide {
+    display: none; }
+
+.select2-close-mask {
+  border: 0;
+  margin: 0;
+  padding: 0;
+  display: block;
+  position: fixed;
+  left: 0;
+  top: 0;
+  min-height: 100%;
+  min-width: 100%;
+  height: auto;
+  width: auto;
+  opacity: 0;
+  z-index: 99;
+  background-color: #fff;
+  filter: alpha(opacity=0); }
+
+.select2-hidden-accessible {
+  border: 0 !important;
+  clip: rect(0 0 0 0) !important;
+  -webkit-clip-path: inset(50%) !important;
+  clip-path: inset(50%) !important;
+  height: 1px !important;
+  overflow: hidden !important;
+  padding: 0 !important;
+  position: absolute !important;
+  width: 1px !important;
+  white-space: nowrap !important; }
+
+.select2-container--default .select2-selection--single {
+  background-color: #fff;
+  border: 1px solid #aaa;
+  border-radius: 4px; }
+  .select2-container--default .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--default .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold; }
+  .select2-container--default .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--default .select2-selection--single .select2-selection__arrow {
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px; }
+    .select2-container--default .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  left: 1px;
+  right: auto; }
+
+.select2-container--default.select2-container--disabled .select2-selection--single {
+  background-color: #eee;
+  cursor: default; }
+  .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
+    display: none; }
+
+.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
+  border-color: transparent transparent #888 transparent;
+  border-width: 0 4px 5px 4px; }
+
+.select2-container--default .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text; }
+  .select2-container--default .select2-selection--multiple .select2-selection__rendered {
+    box-sizing: border-box;
+    list-style: none;
+    margin: 0;
+    padding: 0 5px;
+    width: 100%; }
+    .select2-container--default .select2-selection--multiple .select2-selection__rendered li {
+      list-style: none; }
+  .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
+    color: #999;
+    margin-top: 5px;
+    float: left; }
+  .select2-container--default .select2-selection--multiple .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-top: 5px;
+    margin-right: 10px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
+    color: #999;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #333; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
+  float: right; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+  border: solid black 1px;
+  outline: 0; }
+
+.select2-container--default.select2-container--disabled .select2-selection--multiple {
+  background-color: #eee;
+  cursor: default; }
+
+.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
+  display: none; }
+
+.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--default .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa; }
+
+.select2-container--default .select2-search--inline .select2-search__field {
+  background: transparent;
+  border: none;
+  outline: 0;
+  box-shadow: none;
+  -webkit-appearance: textfield; }
+
+.select2-container--default .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--default .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--default .select2-results__option[aria-disabled=true] {
+  color: #999; }
+
+.select2-container--default .select2-results__option[aria-selected=true] {
+  background-color: #ddd; }
+
+.select2-container--default .select2-results__option .select2-results__option {
+  padding-left: 1em; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__group {
+    padding-left: 0; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -1em;
+    padding-left: 2em; }
+    .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+      margin-left: -2em;
+      padding-left: 3em; }
+      .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+        margin-left: -3em;
+        padding-left: 4em; }
+        .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+          margin-left: -4em;
+          padding-left: 5em; }
+          .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+            margin-left: -5em;
+            padding-left: 6em; }
+
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+  background-color: #5897fb;
+  color: white; }
+
+.select2-container--default .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic .select2-selection--single {
+  background-color: #f7f7f7;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  outline: 0;
+  background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+  .select2-container--classic .select2-selection--single:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--classic .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-right: 10px; }
+  .select2-container--classic .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--classic .select2-selection--single .select2-selection__arrow {
+    background-color: #ddd;
+    border: none;
+    border-left: 1px solid #aaa;
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px;
+    background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
+    background-repeat: repeat-x;
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
+    .select2-container--classic .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  border: none;
+  border-right: 1px solid #aaa;
+  border-radius: 0;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+  left: 1px;
+  right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--single {
+  border: 1px solid #5897fb; }
+  .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
+    background: transparent;
+    border: none; }
+    .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
+      border-color: transparent transparent #888 transparent;
+      border-width: 0 4px 5px 4px; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
+
+.select2-container--classic .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text;
+  outline: 0; }
+  .select2-container--classic .select2-selection--multiple:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__rendered {
+    list-style: none;
+    margin: 0;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__clear {
+    display: none; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
+    color: #888;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #555; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  float: right;
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--multiple {
+  border: 1px solid #5897fb; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--classic .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa;
+  outline: 0; }
+
+.select2-container--classic .select2-search--inline .select2-search__field {
+  outline: 0;
+  box-shadow: none; }
+
+.select2-container--classic .select2-dropdown {
+  background-color: white;
+  border: 1px solid transparent; }
+
+.select2-container--classic .select2-dropdown--above {
+  border-bottom: none; }
+
+.select2-container--classic .select2-dropdown--below {
+  border-top: none; }
+
+.select2-container--classic .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--classic .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--classic .select2-results__option[aria-disabled=true] {
+  color: grey; }
+
+.select2-container--classic .select2-results__option--highlighted[aria-selected] {
+  background-color: #3875d7;
+  color: white; }
+
+.select2-container--classic .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic.select2-container--open .select2-dropdown {
+  border-color: #5897fb; }
diff --git a/src/main/resources/static/ajax/libs/select2/select2.js b/src/main/resources/static/ajax/libs/select2/select2.js
new file mode 100644
index 0000000..4a70caf
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/select2/select2.js
@@ -0,0 +1,5885 @@
+/*!
+ * Select2 4.0.7
+ * https://select2.github.io
+ *
+ * Released under the MIT license
+ * https://github.com/select2/select2/blob/master/LICENSE.md
+ */
+;(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(['jquery'], factory);
+  } else if (typeof module === 'object' && module.exports) {
+    // Node/CommonJS
+    module.exports = function (root, jQuery) {
+      if (jQuery === undefined) {
+        // require('jQuery') returns a factory that requires window to
+        // build a jQuery instance, we normalize how we use modules
+        // that require this pattern but the window provided is a noop
+        // if it's defined (how jquery works)
+        if (typeof window !== 'undefined') {
+          jQuery = require('jquery');
+        }
+        else {
+          jQuery = require('jquery')(root);
+        }
+      }
+      factory(jQuery);
+      return jQuery;
+    };
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+} (function (jQuery) {
+  // This is needed so we can catch the AMD loader configuration and use it
+  // The inner file should be wrapped (by `banner.start.js`) in a function that
+  // returns the AMD loader references.
+  var S2 =(function () {
+  // Restore the Select2 AMD loader so it can be used
+  // Needed mostly in the language files, where the loader is not inserted
+  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
+    var S2 = jQuery.fn.select2.amd;
+  }
+var S2;(function () { if (!S2 || !S2.requirejs) {
+if (!S2) { S2 = {}; } else { require = S2; }
+/**
+ * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
+ * Released under MIT license, http://github.com/requirejs/almond/LICENSE
+ */
+//Going sloppy to avoid 'use strict' string cost, but strict practices should
+//be followed.
+/*global setTimeout: false */
+
+var requirejs, require, define;
+(function (undef) {
+    var main, req, makeMap, handlers,
+        defined = {},
+        waiting = {},
+        config = {},
+        defining = {},
+        hasOwn = Object.prototype.hasOwnProperty,
+        aps = [].slice,
+        jsSuffixRegExp = /\.js$/;
+
+    function hasProp(obj, prop) {
+        return hasOwn.call(obj, prop);
+    }
+
+    /**
+     * Given a relative module name, like ./something, normalize it to
+     * a real name that can be mapped to a path.
+     * @param {String} name the relative name
+     * @param {String} baseName a real name that the name arg is relative
+     * to.
+     * @returns {String} normalized name
+     */
+    function normalize(name, baseName) {
+        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
+            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
+            baseParts = baseName && baseName.split("/"),
+            map = config.map,
+            starMap = (map && map['*']) || {};
+
+        //Adjust any relative paths.
+        if (name) {
+            name = name.split('/');
+            lastIndex = name.length - 1;
+
+            // If wanting node ID compatibility, strip .js from end
+            // of IDs. Have to do this here, and not in nameToUrl
+            // because node allows either .js or non .js to map
+            // to same file.
+            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
+            }
+
+            // Starts with a '.' so need the baseName
+            if (name[0].charAt(0) === '.' && baseParts) {
+                //Convert baseName to array, and lop off the last part,
+                //so that . matches that 'directory' and not name of the baseName's
+                //module. For instance, baseName of 'one/two/three', maps to
+                //'one/two/three.js', but we want the directory, 'one/two' for
+                //this normalization.
+                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+                name = normalizedBaseParts.concat(name);
+            }
+
+            //start trimDots
+            for (i = 0; i < name.length; i++) {
+                part = name[i];
+                if (part === '.') {
+                    name.splice(i, 1);
+                    i -= 1;
+                } else if (part === '..') {
+                    // If at the start, or previous value is still ..,
+                    // keep them so that when converted to a path it may
+                    // still work when converted to a path, even though
+                    // as an ID it is less than ideal. In larger point
+                    // releases, may be better to just kick out an error.
+                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
+                        continue;
+                    } else if (i > 0) {
+                        name.splice(i - 1, 2);
+                        i -= 2;
+                    }
+                }
+            }
+            //end trimDots
+
+            name = name.join('/');
+        }
+
+        //Apply map config if available.
+        if ((baseParts || starMap) && map) {
+            nameParts = name.split('/');
+
+            for (i = nameParts.length; i > 0; i -= 1) {
+                nameSegment = nameParts.slice(0, i).join("/");
+
+                if (baseParts) {
+                    //Find the longest baseName segment match in the config.
+                    //So, do joins on the biggest to smallest lengths of baseParts.
+                    for (j = baseParts.length; j > 0; j -= 1) {
+                        mapValue = map[baseParts.slice(0, j).join('/')];
+
+                        //baseName segment has  config, find if it has one for
+                        //this name.
+                        if (mapValue) {
+                            mapValue = mapValue[nameSegment];
+                            if (mapValue) {
+                                //Match, update name to the new value.
+                                foundMap = mapValue;
+                                foundI = i;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                if (foundMap) {
+                    break;
+                }
+
+                //Check for a star map match, but just hold on to it,
+                //if there is a shorter segment match later in a matching
+                //config, then favor over this star map.
+                if (!foundStarMap && starMap && starMap[nameSegment]) {
+                    foundStarMap = starMap[nameSegment];
+                    starI = i;
+                }
+            }
+
+            if (!foundMap && foundStarMap) {
+                foundMap = foundStarMap;
+                foundI = starI;
+            }
+
+            if (foundMap) {
+                nameParts.splice(0, foundI, foundMap);
+                name = nameParts.join('/');
+            }
+        }
+
+        return name;
+    }
+
+    function makeRequire(relName, forceSync) {
+        return function () {
+            //A version of a require function that passes a moduleName
+            //value for items that may need to
+            //look up paths relative to the moduleName
+            var args = aps.call(arguments, 0);
+
+            //If first arg is not require('string'), and there is only
+            //one arg, it is the array form without a callback. Insert
+            //a null so that the following concat is correct.
+            if (typeof args[0] !== 'string' && args.length === 1) {
+                args.push(null);
+            }
+            return req.apply(undef, args.concat([relName, forceSync]));
+        };
+    }
+
+    function makeNormalize(relName) {
+        return function (name) {
+            return normalize(name, relName);
+        };
+    }
+
+    function makeLoad(depName) {
+        return function (value) {
+            defined[depName] = value;
+        };
+    }
+
+    function callDep(name) {
+        if (hasProp(waiting, name)) {
+            var args = waiting[name];
+            delete waiting[name];
+            defining[name] = true;
+            main.apply(undef, args);
+        }
+
+        if (!hasProp(defined, name) && !hasProp(defining, name)) {
+            throw new Error('No ' + name);
+        }
+        return defined[name];
+    }
+
+    //Turns a plugin!resource to [plugin, resource]
+    //with the plugin being undefined if the name
+    //did not have a plugin prefix.
+    function splitPrefix(name) {
+        var prefix,
+            index = name ? name.indexOf('!') : -1;
+        if (index > -1) {
+            prefix = name.substring(0, index);
+            name = name.substring(index + 1, name.length);
+        }
+        return [prefix, name];
+    }
+
+    //Creates a parts array for a relName where first part is plugin ID,
+    //second part is resource ID. Assumes relName has already been normalized.
+    function makeRelParts(relName) {
+        return relName ? splitPrefix(relName) : [];
+    }
+
+    /**
+     * Makes a name map, normalizing the name, and using a plugin
+     * for normalization if necessary. Grabs a ref to plugin
+     * too, as an optimization.
+     */
+    makeMap = function (name, relParts) {
+        var plugin,
+            parts = splitPrefix(name),
+            prefix = parts[0],
+            relResourceName = relParts[1];
+
+        name = parts[1];
+
+        if (prefix) {
+            prefix = normalize(prefix, relResourceName);
+            plugin = callDep(prefix);
+        }
+
+        //Normalize according
+        if (prefix) {
+            if (plugin && plugin.normalize) {
+                name = plugin.normalize(name, makeNormalize(relResourceName));
+            } else {
+                name = normalize(name, relResourceName);
+            }
+        } else {
+            name = normalize(name, relResourceName);
+            parts = splitPrefix(name);
+            prefix = parts[0];
+            name = parts[1];
+            if (prefix) {
+                plugin = callDep(prefix);
+            }
+        }
+
+        //Using ridiculous property names for space reasons
+        return {
+            f: prefix ? prefix + '!' + name : name, //fullName
+            n: name,
+            pr: prefix,
+            p: plugin
+        };
+    };
+
+    function makeConfig(name) {
+        return function () {
+            return (config && config.config && config.config[name]) || {};
+        };
+    }
+
+    handlers = {
+        require: function (name) {
+            return makeRequire(name);
+        },
+        exports: function (name) {
+            var e = defined[name];
+            if (typeof e !== 'undefined') {
+                return e;
+            } else {
+                return (defined[name] = {});
+            }
+        },
+        module: function (name) {
+            return {
+                id: name,
+                uri: '',
+                exports: defined[name],
+                config: makeConfig(name)
+            };
+        }
+    };
+
+    main = function (name, deps, callback, relName) {
+        var cjsModule, depName, ret, map, i, relParts,
+            args = [],
+            callbackType = typeof callback,
+            usingExports;
+
+        //Use name if no relName
+        relName = relName || name;
+        relParts = makeRelParts(relName);
+
+        //Call the callback to define the module, if necessary.
+        if (callbackType === 'undefined' || callbackType === 'function') {
+            //Pull out the defined dependencies and pass the ordered
+            //values to the callback.
+            //Default to [require, exports, module] if no deps
+            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
+            for (i = 0; i < deps.length; i += 1) {
+                map = makeMap(deps[i], relParts);
+                depName = map.f;
+
+                //Fast path CommonJS standard dependencies.
+                if (depName === "require") {
+                    args[i] = handlers.require(name);
+                } else if (depName === "exports") {
+                    //CommonJS module spec 1.1
+                    args[i] = handlers.exports(name);
+                    usingExports = true;
+                } else if (depName === "module") {
+                    //CommonJS module spec 1.1
+                    cjsModule = args[i] = handlers.module(name);
+                } else if (hasProp(defined, depName) ||
+                           hasProp(waiting, depName) ||
+                           hasProp(defining, depName)) {
+                    args[i] = callDep(depName);
+                } else if (map.p) {
+                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
+                    args[i] = defined[depName];
+                } else {
+                    throw new Error(name + ' missing ' + depName);
+                }
+            }
+
+            ret = callback ? callback.apply(defined[name], args) : undefined;
+
+            if (name) {
+                //If setting exports via "module" is in play,
+                //favor that over return value and exports. After that,
+                //favor a non-undefined return value over exports use.
+                if (cjsModule && cjsModule.exports !== undef &&
+                        cjsModule.exports !== defined[name]) {
+                    defined[name] = cjsModule.exports;
+                } else if (ret !== undef || !usingExports) {
+                    //Use the return value from the function.
+                    defined[name] = ret;
+                }
+            }
+        } else if (name) {
+            //May just be an object definition for the module. Only
+            //worry about defining if have a module name.
+            defined[name] = callback;
+        }
+    };
+
+    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
+        if (typeof deps === "string") {
+            if (handlers[deps]) {
+                //callback in this case is really relName
+                return handlers[deps](callback);
+            }
+            //Just return the module wanted. In this scenario, the
+            //deps arg is the module name, and second arg (if passed)
+            //is just the relName.
+            //Normalize module name, if it contains . or ..
+            return callDep(makeMap(deps, makeRelParts(callback)).f);
+        } else if (!deps.splice) {
+            //deps is a config object, not an array.
+            config = deps;
+            if (config.deps) {
+                req(config.deps, config.callback);
+            }
+            if (!callback) {
+                return;
+            }
+
+            if (callback.splice) {
+                //callback is an array, which means it is a dependency list.
+                //Adjust args if there are dependencies
+                deps = callback;
+                callback = relName;
+                relName = null;
+            } else {
+                deps = undef;
+            }
+        }
+
+        //Support require(['a'])
+        callback = callback || function () {};
+
+        //If relName is a function, it is an errback handler,
+        //so remove it.
+        if (typeof relName === 'function') {
+            relName = forceSync;
+            forceSync = alt;
+        }
+
+        //Simulate async callback;
+        if (forceSync) {
+            main(undef, deps, callback, relName);
+        } else {
+            //Using a non-zero value because of concern for what old browsers
+            //do, and latest browsers "upgrade" to 4 if lower value is used:
+            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
+            //If want a value immediately, use require('id') instead -- something
+            //that works in almond on the global level, but not guaranteed and
+            //unlikely to work in other AMD implementations.
+            setTimeout(function () {
+                main(undef, deps, callback, relName);
+            }, 4);
+        }
+
+        return req;
+    };
+
+    /**
+     * Just drops the config on the floor, but returns req in case
+     * the config return value is used.
+     */
+    req.config = function (cfg) {
+        return req(cfg);
+    };
+
+    /**
+     * Expose module registry for debugging and tooling
+     */
+    requirejs._defined = defined;
+
+    define = function (name, deps, callback) {
+        if (typeof name !== 'string') {
+            throw new Error('See almond README: incorrect module build, no module name');
+        }
+
+        //This module may not have dependencies
+        if (!deps.splice) {
+            //deps is not an array, so probably means
+            //an object literal or factory function for
+            //the value. Adjust args.
+            callback = deps;
+            deps = [];
+        }
+
+        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
+            waiting[name] = [name, deps, callback];
+        }
+    };
+
+    define.amd = {
+        jQuery: true
+    };
+}());
+
+S2.requirejs = requirejs;S2.require = require;S2.define = define;
+}
+}());
+S2.define("almond", function(){});
+
+/* global jQuery:false, $:false */
+S2.define('jquery',[],function () {
+  var _$ = jQuery || $;
+
+  if (_$ == null && console && console.error) {
+    console.error(
+      'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
+      'found. Make sure that you are including jQuery before Select2 on your ' +
+      'web page.'
+    );
+  }
+
+  return _$;
+});
+
+S2.define('select2/utils',[
+  'jquery'
+], function ($) {
+  var Utils = {};
+
+  Utils.Extend = function (ChildClass, SuperClass) {
+    var __hasProp = {}.hasOwnProperty;
+
+    function BaseConstructor () {
+      this.constructor = ChildClass;
+    }
+
+    for (var key in SuperClass) {
+      if (__hasProp.call(SuperClass, key)) {
+        ChildClass[key] = SuperClass[key];
+      }
+    }
+
+    BaseConstructor.prototype = SuperClass.prototype;
+    ChildClass.prototype = new BaseConstructor();
+    ChildClass.__super__ = SuperClass.prototype;
+
+    return ChildClass;
+  };
+
+  function getMethods (theClass) {
+    var proto = theClass.prototype;
+
+    var methods = [];
+
+    for (var methodName in proto) {
+      var m = proto[methodName];
+
+      if (typeof m !== 'function') {
+        continue;
+      }
+
+      if (methodName === 'constructor') {
+        continue;
+      }
+
+      methods.push(methodName);
+    }
+
+    return methods;
+  }
+
+  Utils.Decorate = function (SuperClass, DecoratorClass) {
+    var decoratedMethods = getMethods(DecoratorClass);
+    var superMethods = getMethods(SuperClass);
+
+    function DecoratedClass () {
+      var unshift = Array.prototype.unshift;
+
+      var argCount = DecoratorClass.prototype.constructor.length;
+
+      var calledConstructor = SuperClass.prototype.constructor;
+
+      if (argCount > 0) {
+        unshift.call(arguments, SuperClass.prototype.constructor);
+
+        calledConstructor = DecoratorClass.prototype.constructor;
+      }
+
+      calledConstructor.apply(this, arguments);
+    }
+
+    DecoratorClass.displayName = SuperClass.displayName;
+
+    function ctr () {
+      this.constructor = DecoratedClass;
+    }
+
+    DecoratedClass.prototype = new ctr();
+
+    for (var m = 0; m < superMethods.length; m++) {
+      var superMethod = superMethods[m];
+
+      DecoratedClass.prototype[superMethod] =
+        SuperClass.prototype[superMethod];
+    }
+
+    var calledMethod = function (methodName) {
+      // Stub out the original method if it's not decorating an actual method
+      var originalMethod = function () {};
+
+      if (methodName in DecoratedClass.prototype) {
+        originalMethod = DecoratedClass.prototype[methodName];
+      }
+
+      var decoratedMethod = DecoratorClass.prototype[methodName];
+
+      return function () {
+        var unshift = Array.prototype.unshift;
+
+        unshift.call(arguments, originalMethod);
+
+        return decoratedMethod.apply(this, arguments);
+      };
+    };
+
+    for (var d = 0; d < decoratedMethods.length; d++) {
+      var decoratedMethod = decoratedMethods[d];
+
+      DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
+    }
+
+    return DecoratedClass;
+  };
+
+  var Observable = function () {
+    this.listeners = {};
+  };
+
+  Observable.prototype.on = function (event, callback) {
+    this.listeners = this.listeners || {};
+
+    if (event in this.listeners) {
+      this.listeners[event].push(callback);
+    } else {
+      this.listeners[event] = [callback];
+    }
+  };
+
+  Observable.prototype.trigger = function (event) {
+    var slice = Array.prototype.slice;
+    var params = slice.call(arguments, 1);
+
+    this.listeners = this.listeners || {};
+
+    // Params should always come in as an array
+    if (params == null) {
+      params = [];
+    }
+
+    // If there are no arguments to the event, use a temporary object
+    if (params.length === 0) {
+      params.push({});
+    }
+
+    // Set the `_type` of the first object to the event
+    params[0]._type = event;
+
+    if (event in this.listeners) {
+      this.invoke(this.listeners[event], slice.call(arguments, 1));
+    }
+
+    if ('*' in this.listeners) {
+      this.invoke(this.listeners['*'], arguments);
+    }
+  };
+
+  Observable.prototype.invoke = function (listeners, params) {
+    for (var i = 0, len = listeners.length; i < len; i++) {
+      listeners[i].apply(this, params);
+    }
+  };
+
+  Utils.Observable = Observable;
+
+  Utils.generateChars = function (length) {
+    var chars = '';
+
+    for (var i = 0; i < length; i++) {
+      var randomChar = Math.floor(Math.random() * 36);
+      chars += randomChar.toString(36);
+    }
+
+    return chars;
+  };
+
+  Utils.bind = function (func, context) {
+    return function () {
+      func.apply(context, arguments);
+    };
+  };
+
+  Utils._convertData = function (data) {
+    for (var originalKey in data) {
+      var keys = originalKey.split('-');
+
+      var dataLevel = data;
+
+      if (keys.length === 1) {
+        continue;
+      }
+
+      for (var k = 0; k < keys.length; k++) {
+        var key = keys[k];
+
+        // Lowercase the first letter
+        // By default, dash-separated becomes camelCase
+        key = key.substring(0, 1).toLowerCase() + key.substring(1);
+
+        if (!(key in dataLevel)) {
+          dataLevel[key] = {};
+        }
+
+        if (k == keys.length - 1) {
+          dataLevel[key] = data[originalKey];
+        }
+
+        dataLevel = dataLevel[key];
+      }
+
+      delete data[originalKey];
+    }
+
+    return data;
+  };
+
+  Utils.hasScroll = function (index, el) {
+    // Adapted from the function created by @ShadowScripter
+    // and adapted by @BillBarry on the Stack Exchange Code Review website.
+    // The original code can be found at
+    // http://codereview.stackexchange.com/q/13338
+    // and was designed to be used with the Sizzle selector engine.
+
+    var $el = $(el);
+    var overflowX = el.style.overflowX;
+    var overflowY = el.style.overflowY;
+
+    //Check both x and y declarations
+    if (overflowX === overflowY &&
+        (overflowY === 'hidden' || overflowY === 'visible')) {
+      return false;
+    }
+
+    if (overflowX === 'scroll' || overflowY === 'scroll') {
+      return true;
+    }
+
+    return ($el.innerHeight() < el.scrollHeight ||
+      $el.innerWidth() < el.scrollWidth);
+  };
+
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    // Do not try to escape the markup if it's not a string
+    if (typeof markup !== 'string') {
+      return markup;
+    }
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
+  // Append an array of jQuery nodes to a given element.
+  Utils.appendMany = function ($element, $nodes) {
+    // jQuery 1.7.x does not support $.fn.append() with an array
+    // Fall back to a jQuery object collection using $.fn.add()
+    if ($.fn.jquery.substr(0, 3) === '1.7') {
+      var $jqNodes = $();
+
+      $.map($nodes, function (node) {
+        $jqNodes = $jqNodes.add(node);
+      });
+
+      $nodes = $jqNodes;
+    }
+
+    $element.append($nodes);
+  };
+
+  // Cache objects in Utils.__cache instead of $.data (see #4346)
+  Utils.__cache = {};
+
+  var id = 0;
+  Utils.GetUniqueElementId = function (element) {
+    // Get a unique element Id. If element has no id,
+    // creates a new unique number, stores it in the id
+    // attribute and returns the new id.
+    // If an id already exists, it simply returns it.
+
+    var select2Id = element.getAttribute('data-select2-id');
+    if (select2Id == null) {
+      // If element has id, use it.
+      if (element.id) {
+        select2Id = element.id;
+        element.setAttribute('data-select2-id', select2Id);
+      } else {
+        element.setAttribute('data-select2-id', ++id);
+        select2Id = id.toString();
+      }
+    }
+    return select2Id;
+  };
+
+  Utils.StoreData = function (element, name, value) {
+    // Stores an item in the cache for a specified element.
+    // name is the cache key.
+    var id = Utils.GetUniqueElementId(element);
+    if (!Utils.__cache[id]) {
+      Utils.__cache[id] = {};
+    }
+
+    Utils.__cache[id][name] = value;
+  };
+
+  Utils.GetData = function (element, name) {
+    // Retrieves a value from the cache by its key (name)
+    // name is optional. If no name specified, return
+    // all cache items for the specified element.
+    // and for a specified element.
+    var id = Utils.GetUniqueElementId(element);
+    if (name) {
+      if (Utils.__cache[id]) {
+        if (Utils.__cache[id][name] != null) {
+          return Utils.__cache[id][name];
+        }
+        return $(element).data(name); // Fallback to HTML5 data attribs.
+      }
+      return $(element).data(name); // Fallback to HTML5 data attribs.
+    } else {
+      return Utils.__cache[id];
+    }
+  };
+
+  Utils.RemoveData = function (element) {
+    // Removes all cached items for a specified element.
+    var id = Utils.GetUniqueElementId(element);
+    if (Utils.__cache[id] != null) {
+      delete Utils.__cache[id];
+    }
+  };
+
+  return Utils;
+});
+
+S2.define('select2/results',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Results ($element, options, dataAdapter) {
+    this.$element = $element;
+    this.data = dataAdapter;
+    this.options = options;
+
+    Results.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Results, Utils.Observable);
+
+  Results.prototype.render = function () {
+    var $results = $(
+      '<ul class="select2-results__options" role="tree"></ul>'
+    );
+
+    if (this.options.get('multiple')) {
+      $results.attr('aria-multiselectable', 'true');
+    }
+
+    this.$results = $results;
+
+    return $results;
+  };
+
+  Results.prototype.clear = function () {
+    this.$results.empty();
+  };
+
+  Results.prototype.displayMessage = function (params) {
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    this.clear();
+    this.hideLoading();
+
+    var $message = $(
+      '<li role="treeitem" aria-live="assertive"' +
+      ' class="select2-results__option"></li>'
+    );
+
+    var message = this.options.get('translations').get(params.message);
+
+    $message.append(
+      escapeMarkup(
+        message(params.args)
+      )
+    );
+
+    $message[0].className += ' select2-results__message';
+
+    this.$results.append($message);
+  };
+
+  Results.prototype.hideMessages = function () {
+    this.$results.find('.select2-results__message').remove();
+  };
+
+  Results.prototype.append = function (data) {
+    this.hideLoading();
+
+    var $options = [];
+
+    if (data.results == null || data.results.length === 0) {
+      if (this.$results.children().length === 0) {
+        this.trigger('results:message', {
+          message: 'noResults'
+        });
+      }
+
+      return;
+    }
+
+    data.results = this.sort(data.results);
+
+    for (var d = 0; d < data.results.length; d++) {
+      var item = data.results[d];
+
+      var $option = this.option(item);
+
+      $options.push($option);
+    }
+
+    this.$results.append($options);
+  };
+
+  Results.prototype.position = function ($results, $dropdown) {
+    var $resultsContainer = $dropdown.find('.select2-results');
+    $resultsContainer.append($results);
+  };
+
+  Results.prototype.sort = function (data) {
+    var sorter = this.options.get('sorter');
+
+    return sorter(data);
+  };
+
+  Results.prototype.highlightFirstItem = function () {
+    var $options = this.$results
+      .find('.select2-results__option[aria-selected]');
+
+    var $selected = $options.filter('[aria-selected=true]');
+
+    // Check if there are any selected options
+    if ($selected.length > 0) {
+      // If there are selected options, highlight the first
+      $selected.first().trigger('mouseenter');
+    } else {
+      // If there are no selected options, highlight the first option
+      // in the dropdown
+      $options.first().trigger('mouseenter');
+    }
+
+    this.ensureHighlightVisible();
+  };
+
+  Results.prototype.setClasses = function () {
+    var self = this;
+
+    this.data.current(function (selected) {
+      var selectedIds = $.map(selected, function (s) {
+        return s.id.toString();
+      });
+
+      var $options = self.$results
+        .find('.select2-results__option[aria-selected]');
+
+      $options.each(function () {
+        var $option = $(this);
+
+        var item = Utils.GetData(this, 'data');
+
+        // id needs to be converted to a string when comparing
+        var id = '' + item.id;
+
+        if ((item.element != null && item.element.selected) ||
+            (item.element == null && $.inArray(id, selectedIds) > -1)) {
+          $option.attr('aria-selected', 'true');
+        } else {
+          $option.attr('aria-selected', 'false');
+        }
+      });
+
+    });
+  };
+
+  Results.prototype.showLoading = function (params) {
+    this.hideLoading();
+
+    var loadingMore = this.options.get('translations').get('searching');
+
+    var loading = {
+      disabled: true,
+      loading: true,
+      text: loadingMore(params)
+    };
+    var $loading = this.option(loading);
+    $loading.className += ' loading-results';
+
+    this.$results.prepend($loading);
+  };
+
+  Results.prototype.hideLoading = function () {
+    this.$results.find('.loading-results').remove();
+  };
+
+  Results.prototype.option = function (data) {
+    var option = document.createElement('li');
+    option.className = 'select2-results__option';
+
+    var attrs = {
+      'role': 'treeitem',
+      'aria-selected': 'false'
+    };
+
+    if (data.disabled) {
+      delete attrs['aria-selected'];
+      attrs['aria-disabled'] = 'true';
+    }
+
+    if (data.id == null) {
+      delete attrs['aria-selected'];
+    }
+
+    if (data._resultId != null) {
+      option.id = data._resultId;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    if (data.children) {
+      attrs.role = 'group';
+      attrs['aria-label'] = data.text;
+      delete attrs['aria-selected'];
+    }
+
+    for (var attr in attrs) {
+      var val = attrs[attr];
+
+      option.setAttribute(attr, val);
+    }
+
+    if (data.children) {
+      var $option = $(option);
+
+      var label = document.createElement('strong');
+      label.className = 'select2-results__group';
+
+      var $label = $(label);
+      this.template(data, label);
+
+      var $children = [];
+
+      for (var c = 0; c < data.children.length; c++) {
+        var child = data.children[c];
+
+        var $child = this.option(child);
+
+        $children.push($child);
+      }
+
+      var $childrenContainer = $('<ul></ul>', {
+        'class': 'select2-results__options select2-results__options--nested'
+      });
+
+      $childrenContainer.append($children);
+
+      $option.append(label);
+      $option.append($childrenContainer);
+    } else {
+      this.template(data, option);
+    }
+
+    Utils.StoreData(option, 'data', data);
+
+    return option;
+  };
+
+  Results.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-results';
+
+    this.$results.attr('id', id);
+
+    container.on('results:all', function (params) {
+      self.clear();
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+        self.highlightFirstItem();
+      }
+    });
+
+    container.on('results:append', function (params) {
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+      }
+    });
+
+    container.on('query', function (params) {
+      self.hideMessages();
+      self.showLoading(params);
+    });
+
+    container.on('select', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+
+      if (self.options.get('scrollAfterSelect')) {
+        self.highlightFirstItem();
+      }
+    });
+
+    container.on('unselect', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+
+      if (self.options.get('scrollAfterSelect')) {
+        self.highlightFirstItem();
+      }
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expended="true"
+      self.$results.attr('aria-expanded', 'true');
+      self.$results.attr('aria-hidden', 'false');
+
+      self.setClasses();
+      self.ensureHighlightVisible();
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expended="false"
+      self.$results.attr('aria-expanded', 'false');
+      self.$results.attr('aria-hidden', 'true');
+      self.$results.removeAttr('aria-activedescendant');
+    });
+
+    container.on('results:toggle', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      $highlighted.trigger('mouseup');
+    });
+
+    container.on('results:select', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      var data = Utils.GetData($highlighted[0], 'data');
+
+      if ($highlighted.attr('aria-selected') == 'true') {
+        self.trigger('close', {});
+      } else {
+        self.trigger('select', {
+          data: data
+        });
+      }
+    });
+
+    container.on('results:previous', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      // If we are already at the top, don't move further
+      // If no options, currentIndex will be -1
+      if (currentIndex <= 0) {
+        return;
+      }
+
+      var nextIndex = currentIndex - 1;
+
+      // If none are highlighted, highlight the first
+      if ($highlighted.length === 0) {
+        nextIndex = 0;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top;
+      var nextTop = $next.offset().top;
+      var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextTop - currentOffset < 0) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:next', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      var nextIndex = currentIndex + 1;
+
+      // If we are at the last option, stay there
+      if (nextIndex >= $options.length) {
+        return;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var nextBottom = $next.offset().top + $next.outerHeight(false);
+      var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextBottom > currentOffset) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      params.element.addClass('select2-results__option--highlighted');
+    });
+
+    container.on('results:message', function (params) {
+      self.displayMessage(params);
+    });
+
+    if ($.fn.mousewheel) {
+      this.$results.on('mousewheel', function (e) {
+        var top = self.$results.scrollTop();
+
+        var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
+
+        var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
+        var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
+
+        if (isAtTop) {
+          self.$results.scrollTop(0);
+
+          e.preventDefault();
+          e.stopPropagation();
+        } else if (isAtBottom) {
+          self.$results.scrollTop(
+            self.$results.get(0).scrollHeight - self.$results.height()
+          );
+
+          e.preventDefault();
+          e.stopPropagation();
+        }
+      });
+    }
+
+    this.$results.on('mouseup', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var $this = $(this);
+
+      var data = Utils.GetData(this, 'data');
+
+      if ($this.attr('aria-selected') === 'true') {
+        if (self.options.get('multiple')) {
+          self.trigger('unselect', {
+            originalEvent: evt,
+            data: data
+          });
+        } else {
+          self.trigger('close', {});
+        }
+
+        return;
+      }
+
+      self.trigger('select', {
+        originalEvent: evt,
+        data: data
+      });
+    });
+
+    this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var data = Utils.GetData(this, 'data');
+
+      self.getHighlightedResults()
+          .removeClass('select2-results__option--highlighted');
+
+      self.trigger('results:focus', {
+        data: data,
+        element: $(this)
+      });
+    });
+  };
+
+  Results.prototype.getHighlightedResults = function () {
+    var $highlighted = this.$results
+    .find('.select2-results__option--highlighted');
+
+    return $highlighted;
+  };
+
+  Results.prototype.destroy = function () {
+    this.$results.remove();
+  };
+
+  Results.prototype.ensureHighlightVisible = function () {
+    var $highlighted = this.getHighlightedResults();
+
+    if ($highlighted.length === 0) {
+      return;
+    }
+
+    var $options = this.$results.find('[aria-selected]');
+
+    var currentIndex = $options.index($highlighted);
+
+    var currentOffset = this.$results.offset().top;
+    var nextTop = $highlighted.offset().top;
+    var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
+
+    var offsetDelta = nextTop - currentOffset;
+    nextOffset -= $highlighted.outerHeight(false) * 2;
+
+    if (currentIndex <= 2) {
+      this.$results.scrollTop(0);
+    } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
+      this.$results.scrollTop(nextOffset);
+    }
+  };
+
+  Results.prototype.template = function (result, container) {
+    var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    var content = template(result, container);
+
+    if (content == null) {
+      container.style.display = 'none';
+    } else if (typeof content === 'string') {
+      container.innerHTML = escapeMarkup(content);
+    } else {
+      $(container).append(content);
+    }
+  };
+
+  return Results;
+});
+
+S2.define('select2/keys',[
+
+], function () {
+  var KEYS = {
+    BACKSPACE: 8,
+    TAB: 9,
+    ENTER: 13,
+    SHIFT: 16,
+    CTRL: 17,
+    ALT: 18,
+    ESC: 27,
+    SPACE: 32,
+    PAGE_UP: 33,
+    PAGE_DOWN: 34,
+    END: 35,
+    HOME: 36,
+    LEFT: 37,
+    UP: 38,
+    RIGHT: 39,
+    DOWN: 40,
+    DELETE: 46
+  };
+
+  return KEYS;
+});
+
+S2.define('select2/selection/base',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function BaseSelection ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    BaseSelection.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseSelection, Utils.Observable);
+
+  BaseSelection.prototype.render = function () {
+    var $selection = $(
+      '<span class="select2-selection" role="combobox" ' +
+      ' aria-haspopup="true" aria-expanded="false">' +
+      '</span>'
+    );
+
+    this._tabindex = 0;
+
+    if (Utils.GetData(this.$element[0], 'old-tabindex') != null) {
+      this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex');
+    } else if (this.$element.attr('tabindex') != null) {
+      this._tabindex = this.$element.attr('tabindex');
+    }
+
+    $selection.attr('title', this.$element.attr('title'));
+    $selection.attr('tabindex', this._tabindex);
+
+    this.$selection = $selection;
+
+    return $selection;
+  };
+
+  BaseSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-container';
+    var resultsId = container.id + '-results';
+
+    this.container = container;
+
+    this.$selection.on('focus', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('blur', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      if (evt.which === KEYS.SPACE) {
+        evt.preventDefault();
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      self.$selection.attr('aria-activedescendant', params.data._resultId);
+    });
+
+    container.on('selection:update', function (params) {
+      self.update(params.data);
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expanded="true"
+      self.$selection.attr('aria-expanded', 'true');
+      self.$selection.attr('aria-owns', resultsId);
+
+      self._attachCloseHandler(container);
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expanded="false"
+      self.$selection.attr('aria-expanded', 'false');
+      self.$selection.removeAttr('aria-activedescendant');
+      self.$selection.removeAttr('aria-owns');
+
+      window.setTimeout(function () {
+        self.$selection.focus();
+      }, 0);
+    
+      self._detachCloseHandler(container);
+    });
+
+    container.on('enable', function () {
+      self.$selection.attr('tabindex', self._tabindex);
+    });
+
+    container.on('disable', function () {
+      self.$selection.attr('tabindex', '-1');
+    });
+  };
+
+  BaseSelection.prototype._handleBlur = function (evt) {
+    var self = this;
+
+    // This needs to be delayed as the active element is the body when the tab
+    // key is pressed, possibly along with others.
+    window.setTimeout(function () {
+      // Don't trigger `blur` if the focus is still in the selection
+      if (
+        (document.activeElement == self.$selection[0]) ||
+        ($.contains(self.$selection[0], document.activeElement))
+      ) {
+        return;
+      }
+
+      self.trigger('blur', evt);
+    }, 1);
+  };
+
+  BaseSelection.prototype._attachCloseHandler = function (container) {
+    var self = this;
+
+    $(document.body).on('mousedown.select2.' + container.id, function (e) {
+      var $target = $(e.target);
+
+      var $select = $target.closest('.select2');
+
+      var $all = $('.select2.select2-container--open');
+
+      $all.each(function () {
+        var $this = $(this);
+
+        if (this == $select[0]) {
+          return;
+        }
+
+        var $element = Utils.GetData(this, 'element');
+
+        $element.select2('close');
+      });
+    });
+  };
+
+  BaseSelection.prototype._detachCloseHandler = function (container) {
+    $(document.body).off('mousedown.select2.' + container.id);
+  };
+
+  BaseSelection.prototype.position = function ($selection, $container) {
+    var $selectionContainer = $container.find('.selection');
+    $selectionContainer.append($selection);
+  };
+
+  BaseSelection.prototype.destroy = function () {
+    this._detachCloseHandler(this.container);
+  };
+
+  BaseSelection.prototype.update = function (data) {
+    throw new Error('The `update` method must be defined in child classes.');
+  };
+
+  return BaseSelection;
+});
+
+S2.define('select2/selection/single',[
+  'jquery',
+  './base',
+  '../utils',
+  '../keys'
+], function ($, BaseSelection, Utils, KEYS) {
+  function SingleSelection () {
+    SingleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(SingleSelection, BaseSelection);
+
+  SingleSelection.prototype.render = function () {
+    var $selection = SingleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--single');
+
+    $selection.html(
+      '<span class="select2-selection__rendered"></span>' +
+      '<span class="select2-selection__arrow" role="presentation">' +
+        '<b role="presentation"></b>' +
+      '</span>'
+    );
+
+    return $selection;
+  };
+
+  SingleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    SingleSelection.__super__.bind.apply(this, arguments);
+
+    var id = container.id + '-container';
+
+    this.$selection.find('.select2-selection__rendered')
+      .attr('id', id)
+      .attr('role', 'textbox')
+      .attr('aria-readonly', 'true');
+    this.$selection.attr('aria-labelledby', id);
+
+    this.$selection.on('mousedown', function (evt) {
+      // Only respond to left clicks
+      if (evt.which !== 1) {
+        return;
+      }
+
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on('focus', function (evt) {
+      // User focuses on the container
+    });
+
+    this.$selection.on('blur', function (evt) {
+      // User exits the container
+    });
+
+    container.on('focus', function (evt) {
+      if (!container.isOpen()) {
+        self.$selection.focus();
+      }
+    });
+  };
+
+  SingleSelection.prototype.clear = function () {
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+    $rendered.empty();
+    $rendered.removeAttr('title'); // clear tooltip on empty
+  };
+
+  SingleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  SingleSelection.prototype.selectionContainer = function () {
+    return $('<span></span>');
+  };
+
+  SingleSelection.prototype.update = function (data) {
+    if (data.length === 0) {
+      this.clear();
+      return;
+    }
+
+    var selection = data[0];
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+    var formatted = this.display(selection, $rendered);
+
+    $rendered.empty().append(formatted);
+    $rendered.attr('title', selection.title || selection.text);
+  };
+
+  return SingleSelection;
+});
+
+S2.define('select2/selection/multiple',[
+  'jquery',
+  './base',
+  '../utils'
+], function ($, BaseSelection, Utils) {
+  function MultipleSelection ($element, options) {
+    MultipleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(MultipleSelection, BaseSelection);
+
+  MultipleSelection.prototype.render = function () {
+    var $selection = MultipleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--multiple');
+
+    $selection.html(
+      '<ul class="select2-selection__rendered"></ul>'
+    );
+
+    return $selection;
+  };
+
+  MultipleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    MultipleSelection.__super__.bind.apply(this, arguments);
+
+    this.$selection.on('click', function (evt) {
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on(
+      'click',
+      '.select2-selection__choice__remove',
+      function (evt) {
+        // Ignore the event if it is disabled
+        if (self.options.get('disabled')) {
+          return;
+        }
+
+        var $remove = $(this);
+        var $selection = $remove.parent();
+
+        var data = Utils.GetData($selection[0], 'data');
+
+        self.trigger('unselect', {
+          originalEvent: evt,
+          data: data
+        });
+      }
+    );
+  };
+
+  MultipleSelection.prototype.clear = function () {
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+    $rendered.empty();
+    $rendered.removeAttr('title');
+  };
+
+  MultipleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  MultipleSelection.prototype.selectionContainer = function () {
+    var $container = $(
+      '<li class="select2-selection__choice">' +
+        '<span class="select2-selection__choice__remove" role="presentation">' +
+          '&times;' +
+        '</span>' +
+      '</li>'
+    );
+
+    return $container;
+  };
+
+  MultipleSelection.prototype.update = function (data) {
+    this.clear();
+
+    if (data.length === 0) {
+      return;
+    }
+
+    var $selections = [];
+
+    for (var d = 0; d < data.length; d++) {
+      var selection = data[d];
+
+      var $selection = this.selectionContainer();
+      var formatted = this.display(selection, $selection);
+
+      $selection.append(formatted);
+      $selection.attr('title', selection.title || selection.text);
+
+      Utils.StoreData($selection[0], 'data', selection);
+
+      $selections.push($selection);
+    }
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+
+    Utils.appendMany($rendered, $selections);
+  };
+
+  return MultipleSelection;
+});
+
+S2.define('select2/selection/placeholder',[
+  '../utils'
+], function (Utils) {
+  function Placeholder (decorated, $element, options) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options);
+  }
+
+  Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
+    var $placeholder = this.selectionContainer();
+
+    $placeholder.html(this.display(placeholder));
+    $placeholder.addClass('select2-selection__placeholder')
+                .removeClass('select2-selection__choice');
+
+    return $placeholder;
+  };
+
+  Placeholder.prototype.update = function (decorated, data) {
+    var singlePlaceholder = (
+      data.length == 1 && data[0].id != this.placeholder.id
+    );
+    var multipleSelections = data.length > 1;
+
+    if (multipleSelections || singlePlaceholder) {
+      return decorated.call(this, data);
+    }
+
+    this.clear();
+
+    var $placeholder = this.createPlaceholder(this.placeholder);
+
+    this.$selection.find('.select2-selection__rendered').append($placeholder);
+  };
+
+  return Placeholder;
+});
+
+S2.define('select2/selection/allowClear',[
+  'jquery',
+  '../keys',
+  '../utils'
+], function ($, KEYS, Utils) {
+  function AllowClear () { }
+
+  AllowClear.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    if (this.placeholder == null) {
+      if (this.options.get('debug') && window.console && console.error) {
+        console.error(
+          'Select2: The `allowClear` option should be used in combination ' +
+          'with the `placeholder` option.'
+        );
+      }
+    }
+
+    this.$selection.on('mousedown', '.select2-selection__clear',
+      function (evt) {
+        self._handleClear(evt);
+    });
+
+    container.on('keypress', function (evt) {
+      self._handleKeyboardClear(evt, container);
+    });
+  };
+
+  AllowClear.prototype._handleClear = function (_, evt) {
+    // Ignore the event if it is disabled
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    var $clear = this.$selection.find('.select2-selection__clear');
+
+    // Ignore the event if nothing has been selected
+    if ($clear.length === 0) {
+      return;
+    }
+
+    evt.stopPropagation();
+
+    var data = Utils.GetData($clear[0], 'data');
+
+    var previousVal = this.$element.val();
+    this.$element.val(this.placeholder.id);
+
+    var unselectData = {
+      data: data
+    };
+    this.trigger('clear', unselectData);
+    if (unselectData.prevented) {
+      this.$element.val(previousVal);
+      return;
+    }
+
+    for (var d = 0; d < data.length; d++) {
+      unselectData = {
+        data: data[d]
+      };
+
+      // Trigger the `unselect` event, so people can prevent it from being
+      // cleared.
+      this.trigger('unselect', unselectData);
+
+      // If the event was prevented, don't clear it out.
+      if (unselectData.prevented) {
+        this.$element.val(previousVal);
+        return;
+      }
+    }
+
+    this.$element.trigger('change');
+
+    this.trigger('toggle', {});
+  };
+
+  AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
+    if (container.isOpen()) {
+      return;
+    }
+
+    if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
+      this._handleClear(evt);
+    }
+  };
+
+  AllowClear.prototype.update = function (decorated, data) {
+    decorated.call(this, data);
+
+    if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
+        data.length === 0) {
+      return;
+    }
+
+    var removeAll = this.options.get('translations').get('removeAllItems');   
+
+    var $remove = $(
+      '<span class="select2-selection__clear" title="' + removeAll() +'">' +
+        '&times;' +
+      '</span>'
+    );
+    Utils.StoreData($remove[0], 'data', data);
+
+    this.$selection.find('.select2-selection__rendered').prepend($remove);
+  };
+
+  return AllowClear;
+});
+
+S2.define('select2/selection/search',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function Search (decorated, $element, options) {
+    decorated.call(this, $element, options);
+  }
+
+  Search.prototype.render = function (decorated) {
+    var $search = $(
+      '<li class="select2-search select2-search--inline">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
+      '</li>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    var $rendered = decorated.call(this);
+
+    this._transferTabIndex();
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self.$search.trigger('focus');
+    });
+
+    container.on('close', function () {
+      self.$search.val('');
+      self.$search.removeAttr('aria-activedescendant');
+      self.$search.trigger('focus');
+    });
+
+    container.on('enable', function () {
+      self.$search.prop('disabled', false);
+
+      self._transferTabIndex();
+    });
+
+    container.on('disable', function () {
+      self.$search.prop('disabled', true);
+    });
+
+    container.on('focus', function (evt) {
+      self.$search.trigger('focus');
+    });
+
+    container.on('results:focus', function (params) {
+      self.$search.attr('aria-activedescendant', params.id);
+    });
+
+    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
+      evt.stopPropagation();
+
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+
+      var key = evt.which;
+
+      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
+        var $previousChoice = self.$searchContainer
+          .prev('.select2-selection__choice');
+
+        if ($previousChoice.length > 0) {
+          var item = Utils.GetData($previousChoice[0], 'data');
+
+          self.searchRemoveChoice(item);
+
+          evt.preventDefault();
+        }
+      }
+    });
+
+    // Try to detect the IE version should the `documentMode` property that
+    // is stored on the document. This is only implemented in IE and is
+    // slightly cleaner than doing a user agent check.
+    // This property is not available in Edge, but Edge also doesn't have
+    // this bug.
+    var msie = document.documentMode;
+    var disableInputEvents = msie && msie <= 11;
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$selection.on(
+      'input.searchcheck',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents) {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        // Unbind the duplicated `keyup` event
+        self.$selection.off('keyup.search');
+      }
+    );
+
+    this.$selection.on(
+      'keyup.search input.search',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents && evt.type === 'input') {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        var key = evt.which;
+
+        // We can freely ignore events from modifier keys
+        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
+          return;
+        }
+
+        // Tabbing will be handled during the `keydown` phase
+        if (key == KEYS.TAB) {
+          return;
+        }
+
+        self.handleSearch(evt);
+      }
+    );
+  };
+
+  /**
+   * This method will transfer the tabindex attribute from the rendered
+   * selection to the search box. This allows for the search box to be used as
+   * the primary focus instead of the selection container.
+   *
+   * @private
+   */
+  Search.prototype._transferTabIndex = function (decorated) {
+    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
+    this.$selection.attr('tabindex', '-1');
+  };
+
+  Search.prototype.createPlaceholder = function (decorated, placeholder) {
+    this.$search.attr('placeholder', placeholder.text);
+  };
+
+  Search.prototype.update = function (decorated, data) {
+    var searchHadFocus = this.$search[0] == document.activeElement;
+
+    this.$search.attr('placeholder', '');
+
+    decorated.call(this, data);
+
+    this.$selection.find('.select2-selection__rendered')
+                   .append(this.$searchContainer);
+
+    this.resizeSearch();
+    if (searchHadFocus) {
+      var isTagInput = this.$element.find('[data-select2-tag]').length;
+      if (isTagInput) {
+        // fix IE11 bug where tag input lost focus
+        this.$element.focus();
+      } else {
+        this.$search.focus();
+      }
+    }
+  };
+
+  Search.prototype.handleSearch = function () {
+    this.resizeSearch();
+
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.searchRemoveChoice = function (decorated, item) {
+    this.trigger('unselect', {
+      data: item
+    });
+
+    this.$search.val(item.text);
+    this.handleSearch();
+  };
+
+  Search.prototype.resizeSearch = function () {
+    this.$search.css('width', '25px');
+
+    var width = '';
+
+    if (this.$search.attr('placeholder') !== '') {
+      width = this.$selection.find('.select2-selection__rendered').innerWidth();
+    } else {
+      var minimumWidth = this.$search.val().length + 1;
+
+      width = (minimumWidth * 0.75) + 'em';
+    }
+
+    this.$search.css('width', width);
+  };
+
+  return Search;
+});
+
+S2.define('select2/selection/eventRelay',[
+  'jquery'
+], function ($) {
+  function EventRelay () { }
+
+  EventRelay.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+    var relayEvents = [
+      'open', 'opening',
+      'close', 'closing',
+      'select', 'selecting',
+      'unselect', 'unselecting',
+      'clear', 'clearing'
+    ];
+
+    var preventableEvents = [
+      'opening', 'closing', 'selecting', 'unselecting', 'clearing'
+    ];
+
+    decorated.call(this, container, $container);
+
+    container.on('*', function (name, params) {
+      // Ignore events that should not be relayed
+      if ($.inArray(name, relayEvents) === -1) {
+        return;
+      }
+
+      // The parameters should always be an object
+      params = params || {};
+
+      // Generate the jQuery event for the Select2 event
+      var evt = $.Event('select2:' + name, {
+        params: params
+      });
+
+      self.$element.trigger(evt);
+
+      // Only handle preventable events if it was one
+      if ($.inArray(name, preventableEvents) === -1) {
+        return;
+      }
+
+      params.prevented = evt.isDefaultPrevented();
+    });
+  };
+
+  return EventRelay;
+});
+
+S2.define('select2/translation',[
+  'jquery',
+  'require'
+], function ($, require) {
+  function Translation (dict) {
+    this.dict = dict || {};
+  }
+
+  Translation.prototype.all = function () {
+    return this.dict;
+  };
+
+  Translation.prototype.get = function (key) {
+    return this.dict[key];
+  };
+
+  Translation.prototype.extend = function (translation) {
+    this.dict = $.extend({}, translation.all(), this.dict);
+  };
+
+  // Static functions
+
+  Translation._cache = {};
+
+  Translation.loadPath = function (path) {
+    if (!(path in Translation._cache)) {
+      var translations = require(path);
+
+      Translation._cache[path] = translations;
+    }
+
+    return new Translation(Translation._cache[path]);
+  };
+
+  return Translation;
+});
+
+S2.define('select2/diacritics',[
+
+], function () {
+  var diacritics = {
+    '\u24B6': 'A',
+    '\uFF21': 'A',
+    '\u00C0': 'A',
+    '\u00C1': 'A',
+    '\u00C2': 'A',
+    '\u1EA6': 'A',
+    '\u1EA4': 'A',
+    '\u1EAA': 'A',
+    '\u1EA8': 'A',
+    '\u00C3': 'A',
+    '\u0100': 'A',
+    '\u0102': 'A',
+    '\u1EB0': 'A',
+    '\u1EAE': 'A',
+    '\u1EB4': 'A',
+    '\u1EB2': 'A',
+    '\u0226': 'A',
+    '\u01E0': 'A',
+    '\u00C4': 'A',
+    '\u01DE': 'A',
+    '\u1EA2': 'A',
+    '\u00C5': 'A',
+    '\u01FA': 'A',
+    '\u01CD': 'A',
+    '\u0200': 'A',
+    '\u0202': 'A',
+    '\u1EA0': 'A',
+    '\u1EAC': 'A',
+    '\u1EB6': 'A',
+    '\u1E00': 'A',
+    '\u0104': 'A',
+    '\u023A': 'A',
+    '\u2C6F': 'A',
+    '\uA732': 'AA',
+    '\u00C6': 'AE',
+    '\u01FC': 'AE',
+    '\u01E2': 'AE',
+    '\uA734': 'AO',
+    '\uA736': 'AU',
+    '\uA738': 'AV',
+    '\uA73A': 'AV',
+    '\uA73C': 'AY',
+    '\u24B7': 'B',
+    '\uFF22': 'B',
+    '\u1E02': 'B',
+    '\u1E04': 'B',
+    '\u1E06': 'B',
+    '\u0243': 'B',
+    '\u0182': 'B',
+    '\u0181': 'B',
+    '\u24B8': 'C',
+    '\uFF23': 'C',
+    '\u0106': 'C',
+    '\u0108': 'C',
+    '\u010A': 'C',
+    '\u010C': 'C',
+    '\u00C7': 'C',
+    '\u1E08': 'C',
+    '\u0187': 'C',
+    '\u023B': 'C',
+    '\uA73E': 'C',
+    '\u24B9': 'D',
+    '\uFF24': 'D',
+    '\u1E0A': 'D',
+    '\u010E': 'D',
+    '\u1E0C': 'D',
+    '\u1E10': 'D',
+    '\u1E12': 'D',
+    '\u1E0E': 'D',
+    '\u0110': 'D',
+    '\u018B': 'D',
+    '\u018A': 'D',
+    '\u0189': 'D',
+    '\uA779': 'D',
+    '\u01F1': 'DZ',
+    '\u01C4': 'DZ',
+    '\u01F2': 'Dz',
+    '\u01C5': 'Dz',
+    '\u24BA': 'E',
+    '\uFF25': 'E',
+    '\u00C8': 'E',
+    '\u00C9': 'E',
+    '\u00CA': 'E',
+    '\u1EC0': 'E',
+    '\u1EBE': 'E',
+    '\u1EC4': 'E',
+    '\u1EC2': 'E',
+    '\u1EBC': 'E',
+    '\u0112': 'E',
+    '\u1E14': 'E',
+    '\u1E16': 'E',
+    '\u0114': 'E',
+    '\u0116': 'E',
+    '\u00CB': 'E',
+    '\u1EBA': 'E',
+    '\u011A': 'E',
+    '\u0204': 'E',
+    '\u0206': 'E',
+    '\u1EB8': 'E',
+    '\u1EC6': 'E',
+    '\u0228': 'E',
+    '\u1E1C': 'E',
+    '\u0118': 'E',
+    '\u1E18': 'E',
+    '\u1E1A': 'E',
+    '\u0190': 'E',
+    '\u018E': 'E',
+    '\u24BB': 'F',
+    '\uFF26': 'F',
+    '\u1E1E': 'F',
+    '\u0191': 'F',
+    '\uA77B': 'F',
+    '\u24BC': 'G',
+    '\uFF27': 'G',
+    '\u01F4': 'G',
+    '\u011C': 'G',
+    '\u1E20': 'G',
+    '\u011E': 'G',
+    '\u0120': 'G',
+    '\u01E6': 'G',
+    '\u0122': 'G',
+    '\u01E4': 'G',
+    '\u0193': 'G',
+    '\uA7A0': 'G',
+    '\uA77D': 'G',
+    '\uA77E': 'G',
+    '\u24BD': 'H',
+    '\uFF28': 'H',
+    '\u0124': 'H',
+    '\u1E22': 'H',
+    '\u1E26': 'H',
+    '\u021E': 'H',
+    '\u1E24': 'H',
+    '\u1E28': 'H',
+    '\u1E2A': 'H',
+    '\u0126': 'H',
+    '\u2C67': 'H',
+    '\u2C75': 'H',
+    '\uA78D': 'H',
+    '\u24BE': 'I',
+    '\uFF29': 'I',
+    '\u00CC': 'I',
+    '\u00CD': 'I',
+    '\u00CE': 'I',
+    '\u0128': 'I',
+    '\u012A': 'I',
+    '\u012C': 'I',
+    '\u0130': 'I',
+    '\u00CF': 'I',
+    '\u1E2E': 'I',
+    '\u1EC8': 'I',
+    '\u01CF': 'I',
+    '\u0208': 'I',
+    '\u020A': 'I',
+    '\u1ECA': 'I',
+    '\u012E': 'I',
+    '\u1E2C': 'I',
+    '\u0197': 'I',
+    '\u24BF': 'J',
+    '\uFF2A': 'J',
+    '\u0134': 'J',
+    '\u0248': 'J',
+    '\u24C0': 'K',
+    '\uFF2B': 'K',
+    '\u1E30': 'K',
+    '\u01E8': 'K',
+    '\u1E32': 'K',
+    '\u0136': 'K',
+    '\u1E34': 'K',
+    '\u0198': 'K',
+    '\u2C69': 'K',
+    '\uA740': 'K',
+    '\uA742': 'K',
+    '\uA744': 'K',
+    '\uA7A2': 'K',
+    '\u24C1': 'L',
+    '\uFF2C': 'L',
+    '\u013F': 'L',
+    '\u0139': 'L',
+    '\u013D': 'L',
+    '\u1E36': 'L',
+    '\u1E38': 'L',
+    '\u013B': 'L',
+    '\u1E3C': 'L',
+    '\u1E3A': 'L',
+    '\u0141': 'L',
+    '\u023D': 'L',
+    '\u2C62': 'L',
+    '\u2C60': 'L',
+    '\uA748': 'L',
+    '\uA746': 'L',
+    '\uA780': 'L',
+    '\u01C7': 'LJ',
+    '\u01C8': 'Lj',
+    '\u24C2': 'M',
+    '\uFF2D': 'M',
+    '\u1E3E': 'M',
+    '\u1E40': 'M',
+    '\u1E42': 'M',
+    '\u2C6E': 'M',
+    '\u019C': 'M',
+    '\u24C3': 'N',
+    '\uFF2E': 'N',
+    '\u01F8': 'N',
+    '\u0143': 'N',
+    '\u00D1': 'N',
+    '\u1E44': 'N',
+    '\u0147': 'N',
+    '\u1E46': 'N',
+    '\u0145': 'N',
+    '\u1E4A': 'N',
+    '\u1E48': 'N',
+    '\u0220': 'N',
+    '\u019D': 'N',
+    '\uA790': 'N',
+    '\uA7A4': 'N',
+    '\u01CA': 'NJ',
+    '\u01CB': 'Nj',
+    '\u24C4': 'O',
+    '\uFF2F': 'O',
+    '\u00D2': 'O',
+    '\u00D3': 'O',
+    '\u00D4': 'O',
+    '\u1ED2': 'O',
+    '\u1ED0': 'O',
+    '\u1ED6': 'O',
+    '\u1ED4': 'O',
+    '\u00D5': 'O',
+    '\u1E4C': 'O',
+    '\u022C': 'O',
+    '\u1E4E': 'O',
+    '\u014C': 'O',
+    '\u1E50': 'O',
+    '\u1E52': 'O',
+    '\u014E': 'O',
+    '\u022E': 'O',
+    '\u0230': 'O',
+    '\u00D6': 'O',
+    '\u022A': 'O',
+    '\u1ECE': 'O',
+    '\u0150': 'O',
+    '\u01D1': 'O',
+    '\u020C': 'O',
+    '\u020E': 'O',
+    '\u01A0': 'O',
+    '\u1EDC': 'O',
+    '\u1EDA': 'O',
+    '\u1EE0': 'O',
+    '\u1EDE': 'O',
+    '\u1EE2': 'O',
+    '\u1ECC': 'O',
+    '\u1ED8': 'O',
+    '\u01EA': 'O',
+    '\u01EC': 'O',
+    '\u00D8': 'O',
+    '\u01FE': 'O',
+    '\u0186': 'O',
+    '\u019F': 'O',
+    '\uA74A': 'O',
+    '\uA74C': 'O',
+    '\u0152': 'OE',
+    '\u01A2': 'OI',
+    '\uA74E': 'OO',
+    '\u0222': 'OU',
+    '\u24C5': 'P',
+    '\uFF30': 'P',
+    '\u1E54': 'P',
+    '\u1E56': 'P',
+    '\u01A4': 'P',
+    '\u2C63': 'P',
+    '\uA750': 'P',
+    '\uA752': 'P',
+    '\uA754': 'P',
+    '\u24C6': 'Q',
+    '\uFF31': 'Q',
+    '\uA756': 'Q',
+    '\uA758': 'Q',
+    '\u024A': 'Q',
+    '\u24C7': 'R',
+    '\uFF32': 'R',
+    '\u0154': 'R',
+    '\u1E58': 'R',
+    '\u0158': 'R',
+    '\u0210': 'R',
+    '\u0212': 'R',
+    '\u1E5A': 'R',
+    '\u1E5C': 'R',
+    '\u0156': 'R',
+    '\u1E5E': 'R',
+    '\u024C': 'R',
+    '\u2C64': 'R',
+    '\uA75A': 'R',
+    '\uA7A6': 'R',
+    '\uA782': 'R',
+    '\u24C8': 'S',
+    '\uFF33': 'S',
+    '\u1E9E': 'S',
+    '\u015A': 'S',
+    '\u1E64': 'S',
+    '\u015C': 'S',
+    '\u1E60': 'S',
+    '\u0160': 'S',
+    '\u1E66': 'S',
+    '\u1E62': 'S',
+    '\u1E68': 'S',
+    '\u0218': 'S',
+    '\u015E': 'S',
+    '\u2C7E': 'S',
+    '\uA7A8': 'S',
+    '\uA784': 'S',
+    '\u24C9': 'T',
+    '\uFF34': 'T',
+    '\u1E6A': 'T',
+    '\u0164': 'T',
+    '\u1E6C': 'T',
+    '\u021A': 'T',
+    '\u0162': 'T',
+    '\u1E70': 'T',
+    '\u1E6E': 'T',
+    '\u0166': 'T',
+    '\u01AC': 'T',
+    '\u01AE': 'T',
+    '\u023E': 'T',
+    '\uA786': 'T',
+    '\uA728': 'TZ',
+    '\u24CA': 'U',
+    '\uFF35': 'U',
+    '\u00D9': 'U',
+    '\u00DA': 'U',
+    '\u00DB': 'U',
+    '\u0168': 'U',
+    '\u1E78': 'U',
+    '\u016A': 'U',
+    '\u1E7A': 'U',
+    '\u016C': 'U',
+    '\u00DC': 'U',
+    '\u01DB': 'U',
+    '\u01D7': 'U',
+    '\u01D5': 'U',
+    '\u01D9': 'U',
+    '\u1EE6': 'U',
+    '\u016E': 'U',
+    '\u0170': 'U',
+    '\u01D3': 'U',
+    '\u0214': 'U',
+    '\u0216': 'U',
+    '\u01AF': 'U',
+    '\u1EEA': 'U',
+    '\u1EE8': 'U',
+    '\u1EEE': 'U',
+    '\u1EEC': 'U',
+    '\u1EF0': 'U',
+    '\u1EE4': 'U',
+    '\u1E72': 'U',
+    '\u0172': 'U',
+    '\u1E76': 'U',
+    '\u1E74': 'U',
+    '\u0244': 'U',
+    '\u24CB': 'V',
+    '\uFF36': 'V',
+    '\u1E7C': 'V',
+    '\u1E7E': 'V',
+    '\u01B2': 'V',
+    '\uA75E': 'V',
+    '\u0245': 'V',
+    '\uA760': 'VY',
+    '\u24CC': 'W',
+    '\uFF37': 'W',
+    '\u1E80': 'W',
+    '\u1E82': 'W',
+    '\u0174': 'W',
+    '\u1E86': 'W',
+    '\u1E84': 'W',
+    '\u1E88': 'W',
+    '\u2C72': 'W',
+    '\u24CD': 'X',
+    '\uFF38': 'X',
+    '\u1E8A': 'X',
+    '\u1E8C': 'X',
+    '\u24CE': 'Y',
+    '\uFF39': 'Y',
+    '\u1EF2': 'Y',
+    '\u00DD': 'Y',
+    '\u0176': 'Y',
+    '\u1EF8': 'Y',
+    '\u0232': 'Y',
+    '\u1E8E': 'Y',
+    '\u0178': 'Y',
+    '\u1EF6': 'Y',
+    '\u1EF4': 'Y',
+    '\u01B3': 'Y',
+    '\u024E': 'Y',
+    '\u1EFE': 'Y',
+    '\u24CF': 'Z',
+    '\uFF3A': 'Z',
+    '\u0179': 'Z',
+    '\u1E90': 'Z',
+    '\u017B': 'Z',
+    '\u017D': 'Z',
+    '\u1E92': 'Z',
+    '\u1E94': 'Z',
+    '\u01B5': 'Z',
+    '\u0224': 'Z',
+    '\u2C7F': 'Z',
+    '\u2C6B': 'Z',
+    '\uA762': 'Z',
+    '\u24D0': 'a',
+    '\uFF41': 'a',
+    '\u1E9A': 'a',
+    '\u00E0': 'a',
+    '\u00E1': 'a',
+    '\u00E2': 'a',
+    '\u1EA7': 'a',
+    '\u1EA5': 'a',
+    '\u1EAB': 'a',
+    '\u1EA9': 'a',
+    '\u00E3': 'a',
+    '\u0101': 'a',
+    '\u0103': 'a',
+    '\u1EB1': 'a',
+    '\u1EAF': 'a',
+    '\u1EB5': 'a',
+    '\u1EB3': 'a',
+    '\u0227': 'a',
+    '\u01E1': 'a',
+    '\u00E4': 'a',
+    '\u01DF': 'a',
+    '\u1EA3': 'a',
+    '\u00E5': 'a',
+    '\u01FB': 'a',
+    '\u01CE': 'a',
+    '\u0201': 'a',
+    '\u0203': 'a',
+    '\u1EA1': 'a',
+    '\u1EAD': 'a',
+    '\u1EB7': 'a',
+    '\u1E01': 'a',
+    '\u0105': 'a',
+    '\u2C65': 'a',
+    '\u0250': 'a',
+    '\uA733': 'aa',
+    '\u00E6': 'ae',
+    '\u01FD': 'ae',
+    '\u01E3': 'ae',
+    '\uA735': 'ao',
+    '\uA737': 'au',
+    '\uA739': 'av',
+    '\uA73B': 'av',
+    '\uA73D': 'ay',
+    '\u24D1': 'b',
+    '\uFF42': 'b',
+    '\u1E03': 'b',
+    '\u1E05': 'b',
+    '\u1E07': 'b',
+    '\u0180': 'b',
+    '\u0183': 'b',
+    '\u0253': 'b',
+    '\u24D2': 'c',
+    '\uFF43': 'c',
+    '\u0107': 'c',
+    '\u0109': 'c',
+    '\u010B': 'c',
+    '\u010D': 'c',
+    '\u00E7': 'c',
+    '\u1E09': 'c',
+    '\u0188': 'c',
+    '\u023C': 'c',
+    '\uA73F': 'c',
+    '\u2184': 'c',
+    '\u24D3': 'd',
+    '\uFF44': 'd',
+    '\u1E0B': 'd',
+    '\u010F': 'd',
+    '\u1E0D': 'd',
+    '\u1E11': 'd',
+    '\u1E13': 'd',
+    '\u1E0F': 'd',
+    '\u0111': 'd',
+    '\u018C': 'd',
+    '\u0256': 'd',
+    '\u0257': 'd',
+    '\uA77A': 'd',
+    '\u01F3': 'dz',
+    '\u01C6': 'dz',
+    '\u24D4': 'e',
+    '\uFF45': 'e',
+    '\u00E8': 'e',
+    '\u00E9': 'e',
+    '\u00EA': 'e',
+    '\u1EC1': 'e',
+    '\u1EBF': 'e',
+    '\u1EC5': 'e',
+    '\u1EC3': 'e',
+    '\u1EBD': 'e',
+    '\u0113': 'e',
+    '\u1E15': 'e',
+    '\u1E17': 'e',
+    '\u0115': 'e',
+    '\u0117': 'e',
+    '\u00EB': 'e',
+    '\u1EBB': 'e',
+    '\u011B': 'e',
+    '\u0205': 'e',
+    '\u0207': 'e',
+    '\u1EB9': 'e',
+    '\u1EC7': 'e',
+    '\u0229': 'e',
+    '\u1E1D': 'e',
+    '\u0119': 'e',
+    '\u1E19': 'e',
+    '\u1E1B': 'e',
+    '\u0247': 'e',
+    '\u025B': 'e',
+    '\u01DD': 'e',
+    '\u24D5': 'f',
+    '\uFF46': 'f',
+    '\u1E1F': 'f',
+    '\u0192': 'f',
+    '\uA77C': 'f',
+    '\u24D6': 'g',
+    '\uFF47': 'g',
+    '\u01F5': 'g',
+    '\u011D': 'g',
+    '\u1E21': 'g',
+    '\u011F': 'g',
+    '\u0121': 'g',
+    '\u01E7': 'g',
+    '\u0123': 'g',
+    '\u01E5': 'g',
+    '\u0260': 'g',
+    '\uA7A1': 'g',
+    '\u1D79': 'g',
+    '\uA77F': 'g',
+    '\u24D7': 'h',
+    '\uFF48': 'h',
+    '\u0125': 'h',
+    '\u1E23': 'h',
+    '\u1E27': 'h',
+    '\u021F': 'h',
+    '\u1E25': 'h',
+    '\u1E29': 'h',
+    '\u1E2B': 'h',
+    '\u1E96': 'h',
+    '\u0127': 'h',
+    '\u2C68': 'h',
+    '\u2C76': 'h',
+    '\u0265': 'h',
+    '\u0195': 'hv',
+    '\u24D8': 'i',
+    '\uFF49': 'i',
+    '\u00EC': 'i',
+    '\u00ED': 'i',
+    '\u00EE': 'i',
+    '\u0129': 'i',
+    '\u012B': 'i',
+    '\u012D': 'i',
+    '\u00EF': 'i',
+    '\u1E2F': 'i',
+    '\u1EC9': 'i',
+    '\u01D0': 'i',
+    '\u0209': 'i',
+    '\u020B': 'i',
+    '\u1ECB': 'i',
+    '\u012F': 'i',
+    '\u1E2D': 'i',
+    '\u0268': 'i',
+    '\u0131': 'i',
+    '\u24D9': 'j',
+    '\uFF4A': 'j',
+    '\u0135': 'j',
+    '\u01F0': 'j',
+    '\u0249': 'j',
+    '\u24DA': 'k',
+    '\uFF4B': 'k',
+    '\u1E31': 'k',
+    '\u01E9': 'k',
+    '\u1E33': 'k',
+    '\u0137': 'k',
+    '\u1E35': 'k',
+    '\u0199': 'k',
+    '\u2C6A': 'k',
+    '\uA741': 'k',
+    '\uA743': 'k',
+    '\uA745': 'k',
+    '\uA7A3': 'k',
+    '\u24DB': 'l',
+    '\uFF4C': 'l',
+    '\u0140': 'l',
+    '\u013A': 'l',
+    '\u013E': 'l',
+    '\u1E37': 'l',
+    '\u1E39': 'l',
+    '\u013C': 'l',
+    '\u1E3D': 'l',
+    '\u1E3B': 'l',
+    '\u017F': 'l',
+    '\u0142': 'l',
+    '\u019A': 'l',
+    '\u026B': 'l',
+    '\u2C61': 'l',
+    '\uA749': 'l',
+    '\uA781': 'l',
+    '\uA747': 'l',
+    '\u01C9': 'lj',
+    '\u24DC': 'm',
+    '\uFF4D': 'm',
+    '\u1E3F': 'm',
+    '\u1E41': 'm',
+    '\u1E43': 'm',
+    '\u0271': 'm',
+    '\u026F': 'm',
+    '\u24DD': 'n',
+    '\uFF4E': 'n',
+    '\u01F9': 'n',
+    '\u0144': 'n',
+    '\u00F1': 'n',
+    '\u1E45': 'n',
+    '\u0148': 'n',
+    '\u1E47': 'n',
+    '\u0146': 'n',
+    '\u1E4B': 'n',
+    '\u1E49': 'n',
+    '\u019E': 'n',
+    '\u0272': 'n',
+    '\u0149': 'n',
+    '\uA791': 'n',
+    '\uA7A5': 'n',
+    '\u01CC': 'nj',
+    '\u24DE': 'o',
+    '\uFF4F': 'o',
+    '\u00F2': 'o',
+    '\u00F3': 'o',
+    '\u00F4': 'o',
+    '\u1ED3': 'o',
+    '\u1ED1': 'o',
+    '\u1ED7': 'o',
+    '\u1ED5': 'o',
+    '\u00F5': 'o',
+    '\u1E4D': 'o',
+    '\u022D': 'o',
+    '\u1E4F': 'o',
+    '\u014D': 'o',
+    '\u1E51': 'o',
+    '\u1E53': 'o',
+    '\u014F': 'o',
+    '\u022F': 'o',
+    '\u0231': 'o',
+    '\u00F6': 'o',
+    '\u022B': 'o',
+    '\u1ECF': 'o',
+    '\u0151': 'o',
+    '\u01D2': 'o',
+    '\u020D': 'o',
+    '\u020F': 'o',
+    '\u01A1': 'o',
+    '\u1EDD': 'o',
+    '\u1EDB': 'o',
+    '\u1EE1': 'o',
+    '\u1EDF': 'o',
+    '\u1EE3': 'o',
+    '\u1ECD': 'o',
+    '\u1ED9': 'o',
+    '\u01EB': 'o',
+    '\u01ED': 'o',
+    '\u00F8': 'o',
+    '\u01FF': 'o',
+    '\u0254': 'o',
+    '\uA74B': 'o',
+    '\uA74D': 'o',
+    '\u0275': 'o',
+    '\u0153': 'oe',
+    '\u01A3': 'oi',
+    '\u0223': 'ou',
+    '\uA74F': 'oo',
+    '\u24DF': 'p',
+    '\uFF50': 'p',
+    '\u1E55': 'p',
+    '\u1E57': 'p',
+    '\u01A5': 'p',
+    '\u1D7D': 'p',
+    '\uA751': 'p',
+    '\uA753': 'p',
+    '\uA755': 'p',
+    '\u24E0': 'q',
+    '\uFF51': 'q',
+    '\u024B': 'q',
+    '\uA757': 'q',
+    '\uA759': 'q',
+    '\u24E1': 'r',
+    '\uFF52': 'r',
+    '\u0155': 'r',
+    '\u1E59': 'r',
+    '\u0159': 'r',
+    '\u0211': 'r',
+    '\u0213': 'r',
+    '\u1E5B': 'r',
+    '\u1E5D': 'r',
+    '\u0157': 'r',
+    '\u1E5F': 'r',
+    '\u024D': 'r',
+    '\u027D': 'r',
+    '\uA75B': 'r',
+    '\uA7A7': 'r',
+    '\uA783': 'r',
+    '\u24E2': 's',
+    '\uFF53': 's',
+    '\u00DF': 's',
+    '\u015B': 's',
+    '\u1E65': 's',
+    '\u015D': 's',
+    '\u1E61': 's',
+    '\u0161': 's',
+    '\u1E67': 's',
+    '\u1E63': 's',
+    '\u1E69': 's',
+    '\u0219': 's',
+    '\u015F': 's',
+    '\u023F': 's',
+    '\uA7A9': 's',
+    '\uA785': 's',
+    '\u1E9B': 's',
+    '\u24E3': 't',
+    '\uFF54': 't',
+    '\u1E6B': 't',
+    '\u1E97': 't',
+    '\u0165': 't',
+    '\u1E6D': 't',
+    '\u021B': 't',
+    '\u0163': 't',
+    '\u1E71': 't',
+    '\u1E6F': 't',
+    '\u0167': 't',
+    '\u01AD': 't',
+    '\u0288': 't',
+    '\u2C66': 't',
+    '\uA787': 't',
+    '\uA729': 'tz',
+    '\u24E4': 'u',
+    '\uFF55': 'u',
+    '\u00F9': 'u',
+    '\u00FA': 'u',
+    '\u00FB': 'u',
+    '\u0169': 'u',
+    '\u1E79': 'u',
+    '\u016B': 'u',
+    '\u1E7B': 'u',
+    '\u016D': 'u',
+    '\u00FC': 'u',
+    '\u01DC': 'u',
+    '\u01D8': 'u',
+    '\u01D6': 'u',
+    '\u01DA': 'u',
+    '\u1EE7': 'u',
+    '\u016F': 'u',
+    '\u0171': 'u',
+    '\u01D4': 'u',
+    '\u0215': 'u',
+    '\u0217': 'u',
+    '\u01B0': 'u',
+    '\u1EEB': 'u',
+    '\u1EE9': 'u',
+    '\u1EEF': 'u',
+    '\u1EED': 'u',
+    '\u1EF1': 'u',
+    '\u1EE5': 'u',
+    '\u1E73': 'u',
+    '\u0173': 'u',
+    '\u1E77': 'u',
+    '\u1E75': 'u',
+    '\u0289': 'u',
+    '\u24E5': 'v',
+    '\uFF56': 'v',
+    '\u1E7D': 'v',
+    '\u1E7F': 'v',
+    '\u028B': 'v',
+    '\uA75F': 'v',
+    '\u028C': 'v',
+    '\uA761': 'vy',
+    '\u24E6': 'w',
+    '\uFF57': 'w',
+    '\u1E81': 'w',
+    '\u1E83': 'w',
+    '\u0175': 'w',
+    '\u1E87': 'w',
+    '\u1E85': 'w',
+    '\u1E98': 'w',
+    '\u1E89': 'w',
+    '\u2C73': 'w',
+    '\u24E7': 'x',
+    '\uFF58': 'x',
+    '\u1E8B': 'x',
+    '\u1E8D': 'x',
+    '\u24E8': 'y',
+    '\uFF59': 'y',
+    '\u1EF3': 'y',
+    '\u00FD': 'y',
+    '\u0177': 'y',
+    '\u1EF9': 'y',
+    '\u0233': 'y',
+    '\u1E8F': 'y',
+    '\u00FF': 'y',
+    '\u1EF7': 'y',
+    '\u1E99': 'y',
+    '\u1EF5': 'y',
+    '\u01B4': 'y',
+    '\u024F': 'y',
+    '\u1EFF': 'y',
+    '\u24E9': 'z',
+    '\uFF5A': 'z',
+    '\u017A': 'z',
+    '\u1E91': 'z',
+    '\u017C': 'z',
+    '\u017E': 'z',
+    '\u1E93': 'z',
+    '\u1E95': 'z',
+    '\u01B6': 'z',
+    '\u0225': 'z',
+    '\u0240': 'z',
+    '\u2C6C': 'z',
+    '\uA763': 'z',
+    '\u0386': '\u0391',
+    '\u0388': '\u0395',
+    '\u0389': '\u0397',
+    '\u038A': '\u0399',
+    '\u03AA': '\u0399',
+    '\u038C': '\u039F',
+    '\u038E': '\u03A5',
+    '\u03AB': '\u03A5',
+    '\u038F': '\u03A9',
+    '\u03AC': '\u03B1',
+    '\u03AD': '\u03B5',
+    '\u03AE': '\u03B7',
+    '\u03AF': '\u03B9',
+    '\u03CA': '\u03B9',
+    '\u0390': '\u03B9',
+    '\u03CC': '\u03BF',
+    '\u03CD': '\u03C5',
+    '\u03CB': '\u03C5',
+    '\u03B0': '\u03C5',
+    '\u03CE': '\u03C9',
+    '\u03C2': '\u03C3',
+    '\u2019': '\''
+  };
+
+  return diacritics;
+});
+
+S2.define('select2/data/base',[
+  '../utils'
+], function (Utils) {
+  function BaseAdapter ($element, options) {
+    BaseAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseAdapter, Utils.Observable);
+
+  BaseAdapter.prototype.current = function (callback) {
+    throw new Error('The `current` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.query = function (params, callback) {
+    throw new Error('The `query` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.bind = function (container, $container) {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.destroy = function () {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.generateResultId = function (container, data) {
+    var id = container.id + '-result-';
+
+    id += Utils.generateChars(4);
+
+    if (data.id != null) {
+      id += '-' + data.id.toString();
+    } else {
+      id += '-' + Utils.generateChars(4);
+    }
+    return id;
+  };
+
+  return BaseAdapter;
+});
+
+S2.define('select2/data/select',[
+  './base',
+  '../utils',
+  'jquery'
+], function (BaseAdapter, Utils, $) {
+  function SelectAdapter ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    SelectAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(SelectAdapter, BaseAdapter);
+
+  SelectAdapter.prototype.current = function (callback) {
+    var data = [];
+    var self = this;
+
+    this.$element.find(':selected').each(function () {
+      var $option = $(this);
+
+      var option = self.item($option);
+
+      data.push(option);
+    });
+
+    callback(data);
+  };
+
+  SelectAdapter.prototype.select = function (data) {
+    var self = this;
+
+    data.selected = true;
+
+    // If data.element is a DOM node, use it instead
+    if ($(data.element).is('option')) {
+      data.element.selected = true;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    if (this.$element.prop('multiple')) {
+      this.current(function (currentData) {
+        var val = [];
+
+        data = [data];
+        data.push.apply(data, currentData);
+
+        for (var d = 0; d < data.length; d++) {
+          var id = data[d].id;
+
+          if ($.inArray(id, val) === -1) {
+            val.push(id);
+          }
+        }
+
+        self.$element.val(val);
+        self.$element.trigger('change');
+      });
+    } else {
+      var val = data.id;
+
+      this.$element.val(val);
+      this.$element.trigger('change');
+    }
+  };
+
+  SelectAdapter.prototype.unselect = function (data) {
+    var self = this;
+
+    if (!this.$element.prop('multiple')) {
+      return;
+    }
+
+    data.selected = false;
+
+    if ($(data.element).is('option')) {
+      data.element.selected = false;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    this.current(function (currentData) {
+      var val = [];
+
+      for (var d = 0; d < currentData.length; d++) {
+        var id = currentData[d].id;
+
+        if (id !== data.id && $.inArray(id, val) === -1) {
+          val.push(id);
+        }
+      }
+
+      self.$element.val(val);
+
+      self.$element.trigger('change');
+    });
+  };
+
+  SelectAdapter.prototype.bind = function (container, $container) {
+    var self = this;
+
+    this.container = container;
+
+    container.on('select', function (params) {
+      self.select(params.data);
+    });
+
+    container.on('unselect', function (params) {
+      self.unselect(params.data);
+    });
+  };
+
+  SelectAdapter.prototype.destroy = function () {
+    // Remove anything added to child elements
+    this.$element.find('*').each(function () {
+      // Remove any custom data set by Select2
+      Utils.RemoveData(this);
+    });
+  };
+
+  SelectAdapter.prototype.query = function (params, callback) {
+    var data = [];
+    var self = this;
+
+    var $options = this.$element.children();
+
+    $options.each(function () {
+      var $option = $(this);
+
+      if (!$option.is('option') && !$option.is('optgroup')) {
+        return;
+      }
+
+      var option = self.item($option);
+
+      var matches = self.matches(params, option);
+
+      if (matches !== null) {
+        data.push(matches);
+      }
+    });
+
+    callback({
+      results: data
+    });
+  };
+
+  SelectAdapter.prototype.addOptions = function ($options) {
+    Utils.appendMany(this.$element, $options);
+  };
+
+  SelectAdapter.prototype.option = function (data) {
+    var option;
+
+    if (data.children) {
+      option = document.createElement('optgroup');
+      option.label = data.text;
+    } else {
+      option = document.createElement('option');
+
+      if (option.textContent !== undefined) {
+        option.textContent = data.text;
+      } else {
+        option.innerText = data.text;
+      }
+    }
+
+    if (data.id !== undefined) {
+      option.value = data.id;
+    }
+
+    if (data.disabled) {
+      option.disabled = true;
+    }
+
+    if (data.selected) {
+      option.selected = true;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    var $option = $(option);
+
+    var normalizedData = this._normalizeItem(data);
+    normalizedData.element = option;
+
+    // Override the option's data with the combined data
+    Utils.StoreData(option, 'data', normalizedData);
+
+    return $option;
+  };
+
+  SelectAdapter.prototype.item = function ($option) {
+    var data = {};
+
+    data = Utils.GetData($option[0], 'data');
+
+    if (data != null) {
+      return data;
+    }
+
+    if ($option.is('option')) {
+      data = {
+        id: $option.val(),
+        text: $option.text(),
+        disabled: $option.prop('disabled'),
+        selected: $option.prop('selected'),
+        title: $option.prop('title')
+      };
+    } else if ($option.is('optgroup')) {
+      data = {
+        text: $option.prop('label'),
+        children: [],
+        title: $option.prop('title')
+      };
+
+      var $children = $option.children('option');
+      var children = [];
+
+      for (var c = 0; c < $children.length; c++) {
+        var $child = $($children[c]);
+
+        var child = this.item($child);
+
+        children.push(child);
+      }
+
+      data.children = children;
+    }
+
+    data = this._normalizeItem(data);
+    data.element = $option[0];
+
+    Utils.StoreData($option[0], 'data', data);
+
+    return data;
+  };
+
+  SelectAdapter.prototype._normalizeItem = function (item) {
+    if (item !== Object(item)) {
+      item = {
+        id: item,
+        text: item
+      };
+    }
+
+    item = $.extend({}, {
+      text: ''
+    }, item);
+
+    var defaults = {
+      selected: false,
+      disabled: false
+    };
+
+    if (item.id != null) {
+      item.id = item.id.toString();
+    }
+
+    if (item.text != null) {
+      item.text = item.text.toString();
+    }
+
+    if (item._resultId == null && item.id && this.container != null) {
+      item._resultId = this.generateResultId(this.container, item);
+    }
+
+    return $.extend({}, defaults, item);
+  };
+
+  SelectAdapter.prototype.matches = function (params, data) {
+    var matcher = this.options.get('matcher');
+
+    return matcher(params, data);
+  };
+
+  return SelectAdapter;
+});
+
+S2.define('select2/data/array',[
+  './select',
+  '../utils',
+  'jquery'
+], function (SelectAdapter, Utils, $) {
+  function ArrayAdapter ($element, options) {
+    var data = options.get('data') || [];
+
+    ArrayAdapter.__super__.constructor.call(this, $element, options);
+
+    this.addOptions(this.convertToOptions(data));
+  }
+
+  Utils.Extend(ArrayAdapter, SelectAdapter);
+
+  ArrayAdapter.prototype.select = function (data) {
+    var $option = this.$element.find('option').filter(function (i, elm) {
+      return elm.value == data.id.toString();
+    });
+
+    if ($option.length === 0) {
+      $option = this.option(data);
+
+      this.addOptions($option);
+    }
+
+    ArrayAdapter.__super__.select.call(this, data);
+  };
+
+  ArrayAdapter.prototype.convertToOptions = function (data) {
+    var self = this;
+
+    var $existing = this.$element.find('option');
+    var existingIds = $existing.map(function () {
+      return self.item($(this)).id;
+    }).get();
+
+    var $options = [];
+
+    // Filter out all items except for the one passed in the argument
+    function onlyItem (item) {
+      return function () {
+        return $(this).val() == item.id;
+      };
+    }
+
+    for (var d = 0; d < data.length; d++) {
+      var item = this._normalizeItem(data[d]);
+
+      // Skip items which were pre-loaded, only merge the data
+      if ($.inArray(item.id, existingIds) >= 0) {
+        var $existingOption = $existing.filter(onlyItem(item));
+
+        var existingData = this.item($existingOption);
+        var newData = $.extend(true, {}, item, existingData);
+
+        var $newOption = this.option(newData);
+
+        $existingOption.replaceWith($newOption);
+
+        continue;
+      }
+
+      var $option = this.option(item);
+
+      if (item.children) {
+        var $children = this.convertToOptions(item.children);
+
+        Utils.appendMany($option, $children);
+      }
+
+      $options.push($option);
+    }
+
+    return $options;
+  };
+
+  return ArrayAdapter;
+});
+
+S2.define('select2/data/ajax',[
+  './array',
+  '../utils',
+  'jquery'
+], function (ArrayAdapter, Utils, $) {
+  function AjaxAdapter ($element, options) {
+    this.ajaxOptions = this._applyDefaults(options.get('ajax'));
+
+    if (this.ajaxOptions.processResults != null) {
+      this.processResults = this.ajaxOptions.processResults;
+    }
+
+    AjaxAdapter.__super__.constructor.call(this, $element, options);
+  }
+
+  Utils.Extend(AjaxAdapter, ArrayAdapter);
+
+  AjaxAdapter.prototype._applyDefaults = function (options) {
+    var defaults = {
+      data: function (params) {
+        return $.extend({}, params, {
+          q: params.term
+        });
+      },
+      transport: function (params, success, failure) {
+        var $request = $.ajax(params);
+
+        $request.then(success);
+        $request.fail(failure);
+
+        return $request;
+      }
+    };
+
+    return $.extend({}, defaults, options, true);
+  };
+
+  AjaxAdapter.prototype.processResults = function (results) {
+    return results;
+  };
+
+  AjaxAdapter.prototype.query = function (params, callback) {
+    var matches = [];
+    var self = this;
+
+    if (this._request != null) {
+      // JSONP requests cannot always be aborted
+      if ($.isFunction(this._request.abort)) {
+        this._request.abort();
+      }
+
+      this._request = null;
+    }
+
+    var options = $.extend({
+      type: 'GET'
+    }, this.ajaxOptions);
+
+    if (typeof options.url === 'function') {
+      options.url = options.url.call(this.$element, params);
+    }
+
+    if (typeof options.data === 'function') {
+      options.data = options.data.call(this.$element, params);
+    }
+
+    function request () {
+      var $request = options.transport(options, function (data) {
+        var results = self.processResults(data, params);
+
+        if (self.options.get('debug') && window.console && console.error) {
+          // Check to make sure that the response included a `results` key.
+          if (!results || !results.results || !$.isArray(results.results)) {
+            console.error(
+              'Select2: The AJAX results did not return an array in the ' +
+              '`results` key of the response.'
+            );
+          }
+        }
+
+        callback(results);
+      }, function () {
+        // Attempt to detect if a request was aborted
+        // Only works if the transport exposes a status property
+        if ('status' in $request &&
+            ($request.status === 0 || $request.status === '0')) {
+          return;
+        }
+
+        self.trigger('results:message', {
+          message: 'errorLoading'
+        });
+      });
+
+      self._request = $request;
+    }
+
+    if (this.ajaxOptions.delay && params.term != null) {
+      if (this._queryTimeout) {
+        window.clearTimeout(this._queryTimeout);
+      }
+
+      this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
+    } else {
+      request();
+    }
+  };
+
+  return AjaxAdapter;
+});
+
+S2.define('select2/data/tags',[
+  'jquery'
+], function ($) {
+  function Tags (decorated, $element, options) {
+    var tags = options.get('tags');
+
+    var createTag = options.get('createTag');
+
+    if (createTag !== undefined) {
+      this.createTag = createTag;
+    }
+
+    var insertTag = options.get('insertTag');
+
+    if (insertTag !== undefined) {
+        this.insertTag = insertTag;
+    }
+
+    decorated.call(this, $element, options);
+
+    if ($.isArray(tags)) {
+      for (var t = 0; t < tags.length; t++) {
+        var tag = tags[t];
+        var item = this._normalizeItem(tag);
+
+        var $option = this.option(item);
+
+        this.$element.append($option);
+      }
+    }
+  }
+
+  Tags.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    this._removeOldTags();
+
+    if (params.term == null || params.page != null) {
+      decorated.call(this, params, callback);
+      return;
+    }
+
+    function wrapper (obj, child) {
+      var data = obj.results;
+
+      for (var i = 0; i < data.length; i++) {
+        var option = data[i];
+
+        var checkChildren = (
+          option.children != null &&
+          !wrapper({
+            results: option.children
+          }, true)
+        );
+
+        var optionText = (option.text || '').toUpperCase();
+        var paramsTerm = (params.term || '').toUpperCase();
+
+        var checkText = optionText === paramsTerm;
+
+        if (checkText || checkChildren) {
+          if (child) {
+            return false;
+          }
+
+          obj.data = data;
+          callback(obj);
+
+          return;
+        }
+      }
+
+      if (child) {
+        return true;
+      }
+
+      var tag = self.createTag(params);
+
+      if (tag != null) {
+        var $option = self.option(tag);
+        $option.attr('data-select2-tag', true);
+
+        self.addOptions([$option]);
+
+        self.insertTag(data, tag);
+      }
+
+      obj.results = data;
+
+      callback(obj);
+    }
+
+    decorated.call(this, params, wrapper);
+  };
+
+  Tags.prototype.createTag = function (decorated, params) {
+    var term = $.trim(params.term);
+
+    if (term === '') {
+      return null;
+    }
+
+    return {
+      id: term,
+      text: term
+    };
+  };
+
+  Tags.prototype.insertTag = function (_, data, tag) {
+    data.unshift(tag);
+  };
+
+  Tags.prototype._removeOldTags = function (_) {
+    var tag = this._lastTag;
+
+    var $options = this.$element.find('option[data-select2-tag]');
+
+    $options.each(function () {
+      if (this.selected) {
+        return;
+      }
+
+      $(this).remove();
+    });
+  };
+
+  return Tags;
+});
+
+S2.define('select2/data/tokenizer',[
+  'jquery'
+], function ($) {
+  function Tokenizer (decorated, $element, options) {
+    var tokenizer = options.get('tokenizer');
+
+    if (tokenizer !== undefined) {
+      this.tokenizer = tokenizer;
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  Tokenizer.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    this.$search =  container.dropdown.$search || container.selection.$search ||
+      $container.find('.select2-search__field');
+  };
+
+  Tokenizer.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    function createAndSelect (data) {
+      // Normalize the data object so we can use it for checks
+      var item = self._normalizeItem(data);
+
+      // Check if the data object already exists as a tag
+      // Select it if it doesn't
+      var $existingOptions = self.$element.find('option').filter(function () {
+        return $(this).val() === item.id;
+      });
+
+      // If an existing option wasn't found for it, create the option
+      if (!$existingOptions.length) {
+        var $option = self.option(item);
+        $option.attr('data-select2-tag', true);
+
+        self._removeOldTags();
+        self.addOptions([$option]);
+      }
+
+      // Select the item, now that we know there is an option for it
+      select(item);
+    }
+
+    function select (data) {
+      self.trigger('select', {
+        data: data
+      });
+    }
+
+    params.term = params.term || '';
+
+    var tokenData = this.tokenizer(params, this.options, createAndSelect);
+
+    if (tokenData.term !== params.term) {
+      // Replace the search term if we have the search box
+      if (this.$search.length) {
+        this.$search.val(tokenData.term);
+        this.$search.focus();
+      }
+
+      params.term = tokenData.term;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
+    var separators = options.get('tokenSeparators') || [];
+    var term = params.term;
+    var i = 0;
+
+    var createTag = this.createTag || function (params) {
+      return {
+        id: params.term,
+        text: params.term
+      };
+    };
+
+    while (i < term.length) {
+      var termChar = term[i];
+
+      if ($.inArray(termChar, separators) === -1) {
+        i++;
+
+        continue;
+      }
+
+      var part = term.substr(0, i);
+      var partParams = $.extend({}, params, {
+        term: part
+      });
+
+      var data = createTag(partParams);
+
+      if (data == null) {
+        i++;
+        continue;
+      }
+
+      callback(data);
+
+      // Reset the term to not include the tokenized portion
+      term = term.substr(i + 1) || '';
+      i = 0;
+    }
+
+    return {
+      term: term
+    };
+  };
+
+  return Tokenizer;
+});
+
+S2.define('select2/data/minimumInputLength',[
+
+], function () {
+  function MinimumInputLength (decorated, $e, options) {
+    this.minimumInputLength = options.get('minimumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MinimumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (params.term.length < this.minimumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooShort',
+        args: {
+          minimum: this.minimumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MinimumInputLength;
+});
+
+S2.define('select2/data/maximumInputLength',[
+
+], function () {
+  function MaximumInputLength (decorated, $e, options) {
+    this.maximumInputLength = options.get('maximumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (this.maximumInputLength > 0 &&
+        params.term.length > this.maximumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooLong',
+        args: {
+          maximum: this.maximumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MaximumInputLength;
+});
+
+S2.define('select2/data/maximumSelectionLength',[
+
+], function (){
+  function MaximumSelectionLength (decorated, $e, options) {
+    this.maximumSelectionLength = options.get('maximumSelectionLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumSelectionLength.prototype.query =
+    function (decorated, params, callback) {
+      var self = this;
+
+      this.current(function (currentData) {
+        var count = currentData != null ? currentData.length : 0;
+        if (self.maximumSelectionLength > 0 &&
+          count >= self.maximumSelectionLength) {
+          self.trigger('results:message', {
+            message: 'maximumSelected',
+            args: {
+              maximum: self.maximumSelectionLength
+            }
+          });
+          return;
+        }
+        decorated.call(self, params, callback);
+      });
+  };
+
+  return MaximumSelectionLength;
+});
+
+S2.define('select2/dropdown',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Dropdown ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    Dropdown.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Dropdown, Utils.Observable);
+
+  Dropdown.prototype.render = function () {
+    var $dropdown = $(
+      '<span class="select2-dropdown">' +
+        '<span class="select2-results"></span>' +
+      '</span>'
+    );
+
+    $dropdown.attr('dir', this.options.get('dir'));
+
+    this.$dropdown = $dropdown;
+
+    return $dropdown;
+  };
+
+  Dropdown.prototype.bind = function () {
+    // Should be implemented in subclasses
+  };
+
+  Dropdown.prototype.position = function ($dropdown, $container) {
+    // Should be implemented in subclasses
+  };
+
+  Dropdown.prototype.destroy = function () {
+    // Remove the dropdown from the DOM
+    this.$dropdown.remove();
+  };
+
+  return Dropdown;
+});
+
+S2.define('select2/dropdown/search',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function Search () { }
+
+  Search.prototype.render = function (decorated) {
+    var $rendered = decorated.call(this);
+
+    var $search = $(
+      '<span class="select2-search select2-search--dropdown">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" />' +
+      '</span>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    $rendered.prepend($search);
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    this.$search.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+    });
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$search.on('input', function (evt) {
+      // Unbind the duplicated `keyup` event
+      $(this).off('keyup');
+    });
+
+    this.$search.on('keyup input', function (evt) {
+      self.handleSearch(evt);
+    });
+
+    container.on('open', function () {
+      self.$search.attr('tabindex', 0);
+
+      self.$search.focus();
+
+      window.setTimeout(function () {
+        self.$search.focus();
+      }, 0);
+    });
+
+    container.on('close', function () {
+      self.$search.attr('tabindex', -1);
+
+      self.$search.val('');
+      self.$search.blur();
+    });
+
+    container.on('focus', function () {
+      if (!container.isOpen()) {
+        self.$search.focus();
+      }
+    });
+
+    container.on('results:all', function (params) {
+      if (params.query.term == null || params.query.term === '') {
+        var showSearch = self.showSearch(params);
+
+        if (showSearch) {
+          self.$searchContainer.removeClass('select2-search--hide');
+        } else {
+          self.$searchContainer.addClass('select2-search--hide');
+        }
+      }
+    });
+  };
+
+  Search.prototype.handleSearch = function (evt) {
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.showSearch = function (_, params) {
+    return true;
+  };
+
+  return Search;
+});
+
+S2.define('select2/dropdown/hidePlaceholder',[
+
+], function () {
+  function HidePlaceholder (decorated, $element, options, dataAdapter) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  HidePlaceholder.prototype.append = function (decorated, data) {
+    data.results = this.removePlaceholder(data.results);
+
+    decorated.call(this, data);
+  };
+
+  HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  HidePlaceholder.prototype.removePlaceholder = function (_, data) {
+    var modifiedData = data.slice(0);
+
+    for (var d = data.length - 1; d >= 0; d--) {
+      var item = data[d];
+
+      if (this.placeholder.id === item.id) {
+        modifiedData.splice(d, 1);
+      }
+    }
+
+    return modifiedData;
+  };
+
+  return HidePlaceholder;
+});
+
+S2.define('select2/dropdown/infiniteScroll',[
+  'jquery'
+], function ($) {
+  function InfiniteScroll (decorated, $element, options, dataAdapter) {
+    this.lastParams = {};
+
+    decorated.call(this, $element, options, dataAdapter);
+
+    this.$loadingMore = this.createLoadingMore();
+    this.loading = false;
+  }
+
+  InfiniteScroll.prototype.append = function (decorated, data) {
+    this.$loadingMore.remove();
+    this.loading = false;
+
+    decorated.call(this, data);
+
+    if (this.showLoadingMore(data)) {
+      this.$results.append(this.$loadingMore);
+    }
+  };
+
+  InfiniteScroll.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('query', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    container.on('query:append', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    this.$results.on('scroll', function () {
+      var isLoadMoreVisible = $.contains(
+        document.documentElement,
+        self.$loadingMore[0]
+      );
+
+      if (self.loading || !isLoadMoreVisible) {
+        return;
+      }
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var loadingMoreOffset = self.$loadingMore.offset().top +
+        self.$loadingMore.outerHeight(false);
+
+      if (currentOffset + 50 >= loadingMoreOffset) {
+        self.loadMore();
+      }
+    });
+  };
+
+  InfiniteScroll.prototype.loadMore = function () {
+    this.loading = true;
+
+    var params = $.extend({}, {page: 1}, this.lastParams);
+
+    params.page++;
+
+    this.trigger('query:append', params);
+  };
+
+  InfiniteScroll.prototype.showLoadingMore = function (_, data) {
+    return data.pagination && data.pagination.more;
+  };
+
+  InfiniteScroll.prototype.createLoadingMore = function () {
+    var $option = $(
+      '<li ' +
+      'class="select2-results__option select2-results__option--load-more"' +
+      'role="treeitem" aria-disabled="true"></li>'
+    );
+
+    var message = this.options.get('translations').get('loadingMore');
+
+    $option.html(message(this.lastParams));
+
+    return $option;
+  };
+
+  return InfiniteScroll;
+});
+
+S2.define('select2/dropdown/attachBody',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function AttachBody (decorated, $element, options) {
+    this.$dropdownParent = options.get('dropdownParent') || $(document.body);
+
+    decorated.call(this, $element, options);
+  }
+
+  AttachBody.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    var setupResultsEvents = false;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self._showDropdown();
+      self._attachPositioningHandler(container);
+
+      if (!setupResultsEvents) {
+        setupResultsEvents = true;
+
+        container.on('results:all', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+
+        container.on('results:append', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+      }
+    });
+
+    container.on('close', function () {
+      self._hideDropdown();
+      self._detachPositioningHandler(container);
+    });
+
+    this.$dropdownContainer.on('mousedown', function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  AttachBody.prototype.destroy = function (decorated) {
+    decorated.call(this);
+
+    this.$dropdownContainer.remove();
+  };
+
+  AttachBody.prototype.position = function (decorated, $dropdown, $container) {
+    // Clone all of the container classes
+    $dropdown.attr('class', $container.attr('class'));
+
+    $dropdown.removeClass('select2');
+    $dropdown.addClass('select2-container--open');
+
+    $dropdown.css({
+      position: 'absolute',
+      top: -999999
+    });
+
+    this.$container = $container;
+  };
+
+  AttachBody.prototype.render = function (decorated) {
+    var $container = $('<span></span>');
+
+    var $dropdown = decorated.call(this);
+    $container.append($dropdown);
+
+    this.$dropdownContainer = $container;
+
+    return $container;
+  };
+
+  AttachBody.prototype._hideDropdown = function (decorated) {
+    this.$dropdownContainer.detach();
+  };
+
+  AttachBody.prototype._attachPositioningHandler =
+      function (decorated, container) {
+    var self = this;
+
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.each(function () {
+      Utils.StoreData(this, 'select2-scroll-position', {
+        x: $(this).scrollLeft(),
+        y: $(this).scrollTop()
+      });
+    });
+
+    $watchers.on(scrollEvent, function (ev) {
+      var position = Utils.GetData(this, 'select2-scroll-position');
+      $(this).scrollTop(position.y);
+    });
+
+    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
+      function (e) {
+      self._positionDropdown();
+      self._resizeDropdown();
+    });
+  };
+
+  AttachBody.prototype._detachPositioningHandler =
+      function (decorated, container) {
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.off(scrollEvent);
+
+    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
+  };
+
+  AttachBody.prototype._positionDropdown = function () {
+    var $window = $(window);
+
+    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
+    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
+
+    var newDirection = null;
+
+    var offset = this.$container.offset();
+
+    offset.bottom = offset.top + this.$container.outerHeight(false);
+
+    var container = {
+      height: this.$container.outerHeight(false)
+    };
+
+    container.top = offset.top;
+    container.bottom = offset.top + container.height;
+
+    var dropdown = {
+      height: this.$dropdown.outerHeight(false)
+    };
+
+    var viewport = {
+      top: $window.scrollTop(),
+      bottom: $window.scrollTop() + $window.height()
+    };
+
+    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
+    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
+
+    var css = {
+      left: offset.left,
+      top: container.bottom
+    };
+
+    // Determine what the parent element is to use for calculating the offset
+    var $offsetParent = this.$dropdownParent;
+
+    // For statically positioned elements, we need to get the element
+    // that is determining the offset
+    if ($offsetParent.css('position') === 'static') {
+      $offsetParent = $offsetParent.offsetParent();
+    }
+
+    var parentOffset = $offsetParent.offset();
+
+    css.top -= parentOffset.top;
+    css.left -= parentOffset.left;
+
+    if (!isCurrentlyAbove && !isCurrentlyBelow) {
+      newDirection = 'below';
+    }
+
+    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
+      newDirection = 'above';
+    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
+      newDirection = 'below';
+    }
+
+    if (newDirection == 'above' ||
+      (isCurrentlyAbove && newDirection !== 'below')) {
+      css.top = container.top - parentOffset.top - dropdown.height;
+    }
+
+    if (newDirection != null) {
+      this.$dropdown
+        .removeClass('select2-dropdown--below select2-dropdown--above')
+        .addClass('select2-dropdown--' + newDirection);
+      this.$container
+        .removeClass('select2-container--below select2-container--above')
+        .addClass('select2-container--' + newDirection);
+    }
+
+    this.$dropdownContainer.css(css);
+  };
+
+  AttachBody.prototype._resizeDropdown = function () {
+    var css = {
+      width: this.$container.outerWidth(false) + 'px'
+    };
+
+    if (this.options.get('dropdownAutoWidth')) {
+      css.minWidth = css.width;
+      css.position = 'relative';
+      css.width = 'auto';
+    }
+
+    this.$dropdown.css(css);
+  };
+
+  AttachBody.prototype._showDropdown = function (decorated) {
+    this.$dropdownContainer.appendTo(this.$dropdownParent);
+
+    this._positionDropdown();
+    this._resizeDropdown();
+  };
+
+  return AttachBody;
+});
+
+S2.define('select2/dropdown/minimumResultsForSearch',[
+
+], function () {
+  function countResults (data) {
+    var count = 0;
+
+    for (var d = 0; d < data.length; d++) {
+      var item = data[d];
+
+      if (item.children) {
+        count += countResults(item.children);
+      } else {
+        count++;
+      }
+    }
+
+    return count;
+  }
+
+  function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
+    this.minimumResultsForSearch = options.get('minimumResultsForSearch');
+
+    if (this.minimumResultsForSearch < 0) {
+      this.minimumResultsForSearch = Infinity;
+    }
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
+    if (countResults(params.data.results) < this.minimumResultsForSearch) {
+      return false;
+    }
+
+    return decorated.call(this, params);
+  };
+
+  return MinimumResultsForSearch;
+});
+
+S2.define('select2/dropdown/selectOnClose',[
+  '../utils'
+], function (Utils) {
+  function SelectOnClose () { }
+
+  SelectOnClose.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('close', function (params) {
+      self._handleSelectOnClose(params);
+    });
+  };
+
+  SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
+    if (params && params.originalSelect2Event != null) {
+      var event = params.originalSelect2Event;
+
+      // Don't select an item if the close event was triggered from a select or
+      // unselect event
+      if (event._type === 'select' || event._type === 'unselect') {
+        return;
+      }
+    }
+
+    var $highlightedResults = this.getHighlightedResults();
+
+    // Only select highlighted results
+    if ($highlightedResults.length < 1) {
+      return;
+    }
+
+    var data = Utils.GetData($highlightedResults[0], 'data');
+
+    // Don't re-select already selected resulte
+    if (
+      (data.element != null && data.element.selected) ||
+      (data.element == null && data.selected)
+    ) {
+      return;
+    }
+
+    this.trigger('select', {
+        data: data
+    });
+  };
+
+  return SelectOnClose;
+});
+
+S2.define('select2/dropdown/closeOnSelect',[
+
+], function () {
+  function CloseOnSelect () { }
+
+  CloseOnSelect.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('select', function (evt) {
+      self._selectTriggered(evt);
+    });
+
+    container.on('unselect', function (evt) {
+      self._selectTriggered(evt);
+    });
+  };
+
+  CloseOnSelect.prototype._selectTriggered = function (_, evt) {
+    var originalEvent = evt.originalEvent;
+
+    // Don't close if the control key is being held
+    if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) {
+      return;
+    }
+
+    this.trigger('close', {
+      originalEvent: originalEvent,
+      originalSelect2Event: evt
+    });
+  };
+
+  return CloseOnSelect;
+});
+
+S2.define('select2/i18n/en',[],function () {
+  // English
+  return {
+    errorLoading: function () {
+      return '无法载入结果';
+    },
+    inputTooLong: function (args) {
+      var overChars = args.input.length - args.maximum;
+
+      var message = '请删除' + overChars + '个字符';
+
+      if (overChars != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    inputTooShort: function (args) {
+      var remainingChars = args.minimum - args.input.length;
+
+      var message = '请再输入至少' + remainingChars + '个字符';
+
+      return message;
+    },
+    loadingMore: function () {
+      return '载入更多结果…';
+    },
+    maximumSelected: function (args) {
+      var message = '最多只能' + args.maximum + '个选项';
+
+      if (args.maximum != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    noResults: function () {
+      return '未找到结果';
+    },
+    searching: function () {
+      return '搜索中…';
+    },
+    removeAllItems: function () {
+      return '删除所有项目';
+    }
+  };
+});
+
+S2.define('select2/defaults',[
+  'jquery',
+  'require',
+
+  './results',
+
+  './selection/single',
+  './selection/multiple',
+  './selection/placeholder',
+  './selection/allowClear',
+  './selection/search',
+  './selection/eventRelay',
+
+  './utils',
+  './translation',
+  './diacritics',
+
+  './data/select',
+  './data/array',
+  './data/ajax',
+  './data/tags',
+  './data/tokenizer',
+  './data/minimumInputLength',
+  './data/maximumInputLength',
+  './data/maximumSelectionLength',
+
+  './dropdown',
+  './dropdown/search',
+  './dropdown/hidePlaceholder',
+  './dropdown/infiniteScroll',
+  './dropdown/attachBody',
+  './dropdown/minimumResultsForSearch',
+  './dropdown/selectOnClose',
+  './dropdown/closeOnSelect',
+
+  './i18n/en'
+], function ($, require,
+
+             ResultsList,
+
+             SingleSelection, MultipleSelection, Placeholder, AllowClear,
+             SelectionSearch, EventRelay,
+
+             Utils, Translation, DIACRITICS,
+
+             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
+             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
+
+             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
+             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
+
+             EnglishTranslation) {
+  function Defaults () {
+    this.reset();
+  }
+
+  Defaults.prototype.apply = function (options) {
+    options = $.extend(true, {}, this.defaults, options);
+
+    if (options.dataAdapter == null) {
+      if (options.ajax != null) {
+        options.dataAdapter = AjaxData;
+      } else if (options.data != null) {
+        options.dataAdapter = ArrayData;
+      } else {
+        options.dataAdapter = SelectData;
+      }
+
+      if (options.minimumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MinimumInputLength
+        );
+      }
+
+      if (options.maximumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumInputLength
+        );
+      }
+
+      if (options.maximumSelectionLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumSelectionLength
+        );
+      }
+
+      if (options.tags) {
+        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
+      }
+
+      if (options.tokenSeparators != null || options.tokenizer != null) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Tokenizer
+        );
+      }
+
+      if (options.query != null) {
+        var Query = require(options.amdBase + 'compat/query');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Query
+        );
+      }
+
+      if (options.initSelection != null) {
+        var InitSelection = require(options.amdBase + 'compat/initSelection');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          InitSelection
+        );
+      }
+    }
+
+    if (options.resultsAdapter == null) {
+      options.resultsAdapter = ResultsList;
+
+      if (options.ajax != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          InfiniteScroll
+        );
+      }
+
+      if (options.placeholder != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          HidePlaceholder
+        );
+      }
+
+      if (options.selectOnClose) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          SelectOnClose
+        );
+      }
+    }
+
+    if (options.dropdownAdapter == null) {
+      if (options.multiple) {
+        options.dropdownAdapter = Dropdown;
+      } else {
+        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
+
+        options.dropdownAdapter = SearchableDropdown;
+      }
+
+      if (options.minimumResultsForSearch !== 0) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          MinimumResultsForSearch
+        );
+      }
+
+      if (options.closeOnSelect) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          CloseOnSelect
+        );
+      }
+
+      if (
+        options.dropdownCssClass != null ||
+        options.dropdownCss != null ||
+        options.adaptDropdownCssClass != null
+      ) {
+        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
+
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          DropdownCSS
+        );
+      }
+
+      options.dropdownAdapter = Utils.Decorate(
+        options.dropdownAdapter,
+        AttachBody
+      );
+    }
+
+    if (options.selectionAdapter == null) {
+      if (options.multiple) {
+        options.selectionAdapter = MultipleSelection;
+      } else {
+        options.selectionAdapter = SingleSelection;
+      }
+
+      // Add the placeholder mixin if a placeholder was specified
+      if (options.placeholder != null) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          Placeholder
+        );
+      }
+
+      if (options.allowClear) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          AllowClear
+        );
+      }
+
+      if (options.multiple) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          SelectionSearch
+        );
+      }
+
+      if (
+        options.containerCssClass != null ||
+        options.containerCss != null ||
+        options.adaptContainerCssClass != null
+      ) {
+        var ContainerCSS = require(options.amdBase + 'compat/containerCss');
+
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          ContainerCSS
+        );
+      }
+
+      options.selectionAdapter = Utils.Decorate(
+        options.selectionAdapter,
+        EventRelay
+      );
+    }
+
+    if (typeof options.language === 'string') {
+      // Check if the language is specified with a region
+      if (options.language.indexOf('-') > 0) {
+        // Extract the region information if it is included
+        var languageParts = options.language.split('-');
+        var baseLanguage = languageParts[0];
+
+        options.language = [options.language, baseLanguage];
+      } else {
+        options.language = [options.language];
+      }
+    }
+
+    if ($.isArray(options.language)) {
+      var languages = new Translation();
+      options.language.push('en');
+
+      var languageNames = options.language;
+
+      for (var l = 0; l < languageNames.length; l++) {
+        var name = languageNames[l];
+        var language = {};
+
+        try {
+          // Try to load it with the original name
+          language = Translation.loadPath(name);
+        } catch (e) {
+          try {
+            // If we couldn't load it, check if it wasn't the full path
+            name = this.defaults.amdLanguageBase + name;
+            language = Translation.loadPath(name);
+          } catch (ex) {
+            // The translation could not be loaded at all. Sometimes this is
+            // because of a configuration problem, other times this can be
+            // because of how Select2 helps load all possible translation files.
+            if (options.debug && window.console && console.warn) {
+              console.warn(
+                'Select2: The language file for "' + name + '" could not be ' +
+                'automatically loaded. A fallback will be used instead.'
+              );
+            }
+
+            continue;
+          }
+        }
+
+        languages.extend(language);
+      }
+
+      options.translations = languages;
+    } else {
+      var baseTranslation = Translation.loadPath(
+        this.defaults.amdLanguageBase + 'en'
+      );
+      var customTranslation = new Translation(options.language);
+
+      customTranslation.extend(baseTranslation);
+
+      options.translations = customTranslation;
+    }
+
+    return options;
+  };
+
+  Defaults.prototype.reset = function () {
+    function stripDiacritics (text) {
+      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
+      function match(a) {
+        return DIACRITICS[a] || a;
+      }
+
+      return text.replace(/[^\u0000-\u007E]/g, match);
+    }
+
+    function matcher (params, data) {
+      // Always return the object if there is nothing to compare
+      if ($.trim(params.term) === '') {
+        return data;
+      }
+
+      // Do a recursive check for options with children
+      if (data.children && data.children.length > 0) {
+        // Clone the data object if there are children
+        // This is required as we modify the object to remove any non-matches
+        var match = $.extend(true, {}, data);
+
+        // Check each child of the option
+        for (var c = data.children.length - 1; c >= 0; c--) {
+          var child = data.children[c];
+
+          var matches = matcher(params, child);
+
+          // If there wasn't a match, remove the object in the array
+          if (matches == null) {
+            match.children.splice(c, 1);
+          }
+        }
+
+        // If any children matched, return the new object
+        if (match.children.length > 0) {
+          return match;
+        }
+
+        // If there were no matching children, check just the plain object
+        return matcher(params, match);
+      }
+
+      var original = stripDiacritics(data.text).toUpperCase();
+      var term = stripDiacritics(params.term).toUpperCase();
+
+      // Check if the text contains the term
+      if (original.indexOf(term) > -1) {
+        return data;
+      }
+
+      // If it doesn't contain the term, don't return anything
+      return null;
+    }
+
+    this.defaults = {
+      amdBase: './',
+      amdLanguageBase: './i18n/',
+      closeOnSelect: true,
+      debug: false,
+      dropdownAutoWidth: false,
+      escapeMarkup: Utils.escapeMarkup,
+      language: EnglishTranslation,
+      matcher: matcher,
+      minimumInputLength: 0,
+      maximumInputLength: 0,
+      maximumSelectionLength: 0,
+      minimumResultsForSearch: 0,
+      selectOnClose: false,
+      scrollAfterSelect: false,
+      sorter: function (data) {
+        return data;
+      },
+      templateResult: function (result) {
+        return result.text;
+      },
+      templateSelection: function (selection) {
+        return selection.text;
+      },
+      theme: 'default',
+      width: '100%'
+    };
+  };
+
+  Defaults.prototype.set = function (key, value) {
+    var camelKey = $.camelCase(key);
+
+    var data = {};
+    data[camelKey] = value;
+
+    var convertedData = Utils._convertData(data);
+
+    $.extend(true, this.defaults, convertedData);
+  };
+
+  var defaults = new Defaults();
+
+  return defaults;
+});
+
+S2.define('select2/options',[
+  'require',
+  'jquery',
+  './defaults',
+  './utils'
+], function (require, $, Defaults, Utils) {
+  function Options (options, $element) {
+    this.options = options;
+
+    if ($element != null) {
+      this.fromElement($element);
+    }
+
+    this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
+  }
+
+  Options.prototype.fromElement = function ($e) {
+    var excludedData = ['select2'];
+
+    if (this.options.multiple == null) {
+      this.options.multiple = $e.prop('multiple');
+    }
+
+    if (this.options.disabled == null) {
+      this.options.disabled = $e.prop('disabled');
+    }
+
+    if (this.options.language == null) {
+      if ($e.prop('lang')) {
+        this.options.language = $e.prop('lang').toLowerCase();
+      } else if ($e.closest('[lang]').prop('lang')) {
+        this.options.language = $e.closest('[lang]').prop('lang');
+      }
+    }
+
+    if (this.options.dir == null) {
+      if ($e.prop('dir')) {
+        this.options.dir = $e.prop('dir');
+      } else if ($e.closest('[dir]').prop('dir')) {
+        this.options.dir = $e.closest('[dir]').prop('dir');
+      } else {
+        this.options.dir = 'ltr';
+      }
+    }
+
+    $e.prop('disabled', this.options.disabled);
+    $e.prop('multiple', this.options.multiple);
+
+    if (Utils.GetData($e[0], 'select2Tags')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-select2-tags` attribute has been changed to ' +
+          'use the `data-data` and `data-tags="true"` attributes and will be ' +
+          'removed in future versions of Select2.'
+        );
+      }
+
+      Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags'));
+      Utils.StoreData($e[0], 'tags', true);
+    }
+
+    if (Utils.GetData($e[0], 'ajaxUrl')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-ajax-url` attribute has been changed to ' +
+          '`data-ajax--url` and support for the old attribute will be removed' +
+          ' in future versions of Select2.'
+        );
+      }
+
+      $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl'));
+      Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl'));
+    }
+
+    var dataset = {};
+
+    function upperCaseLetter(_, letter) {
+      return letter.toUpperCase();
+    }
+
+    // Pre-load all of the attributes which are prefixed with `data-`
+    for (var attr = 0; attr < $e[0].attributes.length; attr++) {
+      var attributeName = $e[0].attributes[attr].name;
+      var prefix = 'data-';
+
+      if (attributeName.substr(0, prefix.length) == prefix) {
+        // Get the contents of the attribute after `data-`
+        var dataName = attributeName.substring(prefix.length);
+
+        // Get the data contents from the consistent source
+        // This is more than likely the jQuery data helper
+        var dataValue = Utils.GetData($e[0], dataName);
+
+        // camelCase the attribute name to match the spec
+        var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter);
+
+        // Store the data attribute contents into the dataset since
+        dataset[camelDataName] = dataValue;
+      }
+    }
+
+    // Prefer the element's `dataset` attribute if it exists
+    // jQuery 1.x does not correctly handle data attributes with multiple dashes
+    if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
+      dataset = $.extend(true, {}, $e[0].dataset, dataset);
+    }
+
+    // Prefer our internal data cache if it exists
+    var data = $.extend(true, {}, Utils.GetData($e[0]), dataset);
+
+    data = Utils._convertData(data);
+
+    for (var key in data) {
+      if ($.inArray(key, excludedData) > -1) {
+        continue;
+      }
+
+      if ($.isPlainObject(this.options[key])) {
+        $.extend(this.options[key], data[key]);
+      } else {
+        this.options[key] = data[key];
+      }
+    }
+
+    return this;
+  };
+
+  Options.prototype.get = function (key) {
+    return this.options[key];
+  };
+
+  Options.prototype.set = function (key, val) {
+    this.options[key] = val;
+  };
+
+  return Options;
+});
+
+S2.define('select2/core',[
+  'jquery',
+  './options',
+  './utils',
+  './keys'
+], function ($, Options, Utils, KEYS) {
+  var Select2 = function ($element, options) {
+    if (Utils.GetData($element[0], 'select2') != null) {
+      Utils.GetData($element[0], 'select2').destroy();
+    }
+
+    this.$element = $element;
+
+    this.id = this._generateId($element);
+
+    options = options || {};
+
+    this.options = new Options(options, $element);
+
+    Select2.__super__.constructor.call(this);
+
+    // Set up the tabindex
+
+    var tabindex = $element.attr('tabindex') || 0;
+    Utils.StoreData($element[0], 'old-tabindex', tabindex);
+    $element.attr('tabindex', '-1');
+
+    // Set up containers and adapters
+
+    var DataAdapter = this.options.get('dataAdapter');
+    this.dataAdapter = new DataAdapter($element, this.options);
+
+    var $container = this.render();
+
+    this._placeContainer($container);
+
+    var SelectionAdapter = this.options.get('selectionAdapter');
+    this.selection = new SelectionAdapter($element, this.options);
+    this.$selection = this.selection.render();
+
+    this.selection.position(this.$selection, $container);
+
+    var DropdownAdapter = this.options.get('dropdownAdapter');
+    this.dropdown = new DropdownAdapter($element, this.options);
+    this.$dropdown = this.dropdown.render();
+
+    this.dropdown.position(this.$dropdown, $container);
+
+    var ResultsAdapter = this.options.get('resultsAdapter');
+    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
+    this.$results = this.results.render();
+
+    this.results.position(this.$results, this.$dropdown);
+
+    // Bind events
+
+    var self = this;
+
+    // Bind the container to all of the adapters
+    this._bindAdapters();
+
+    // Register any DOM event handlers
+    this._registerDomEvents();
+
+    // Register any internal event handlers
+    this._registerDataEvents();
+    this._registerSelectionEvents();
+    this._registerDropdownEvents();
+    this._registerResultsEvents();
+    this._registerEvents();
+
+    // Set the initial state
+    this.dataAdapter.current(function (initialData) {
+      self.trigger('selection:update', {
+        data: initialData
+      });
+    });
+
+    // Hide the original select
+    $element.addClass('select2-hidden-accessible');
+    $element.attr('aria-hidden', 'true');
+
+    // Synchronize any monitored attributes
+    this._syncAttributes();
+
+    Utils.StoreData($element[0], 'select2', this);
+
+    // Ensure backwards compatibility with $element.data('select2').
+    $element.data('select2', this);
+  };
+
+  Utils.Extend(Select2, Utils.Observable);
+
+  Select2.prototype._generateId = function ($element) {
+    var id = '';
+
+    if ($element.attr('id') != null) {
+      id = $element.attr('id');
+    } else if ($element.attr('name') != null) {
+      id = $element.attr('name') + '-' + Utils.generateChars(2);
+    } else {
+      id = Utils.generateChars(4);
+    }
+
+    id = id.replace(/(:|\.|\[|\]|,)/g, '');
+    id = 'select2-' + id;
+
+    return id;
+  };
+
+  Select2.prototype._placeContainer = function ($container) {
+    $container.insertAfter(this.$element);
+
+    var width = this._resolveWidth(this.$element, this.options.get('width'));
+
+    if (width != null) {
+      $container.css('width', width);
+    }
+  };
+
+  Select2.prototype._resolveWidth = function ($element, method) {
+    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
+
+    if (method == 'resolve') {
+      var styleWidth = this._resolveWidth($element, 'style');
+
+      if (styleWidth != null) {
+        return styleWidth;
+      }
+
+      return this._resolveWidth($element, 'element');
+    }
+
+    if (method == 'element') {
+      var elementWidth = $element.outerWidth(false);
+
+      if (elementWidth <= 0) {
+        return 'auto';
+      }
+
+      return elementWidth + 'px';
+    }
+
+    if (method == 'style') {
+      var style = $element.attr('style');
+
+      if (typeof(style) !== 'string') {
+        return null;
+      }
+
+      var attrs = style.split(';');
+
+      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
+        var attr = attrs[i].replace(/\s/g, '');
+        var matches = attr.match(WIDTH);
+
+        if (matches !== null && matches.length >= 1) {
+          return matches[1];
+        }
+      }
+
+      return null;
+    }
+
+    return method;
+  };
+
+  Select2.prototype._bindAdapters = function () {
+    this.dataAdapter.bind(this, this.$container);
+    this.selection.bind(this, this.$container);
+
+    this.dropdown.bind(this, this.$container);
+    this.results.bind(this, this.$container);
+  };
+
+  Select2.prototype._registerDomEvents = function () {
+    var self = this;
+
+    this.$element.on('change.select2', function () {
+      self.dataAdapter.current(function (data) {
+        self.trigger('selection:update', {
+          data: data
+        });
+      });
+    });
+
+    this.$element.on('focus.select2', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this._syncA = Utils.bind(this._syncAttributes, this);
+    this._syncS = Utils.bind(this._syncSubtree, this);
+
+    if (this.$element[0].attachEvent) {
+      this.$element[0].attachEvent('onpropertychange', this._syncA);
+    }
+
+    var observer = window.MutationObserver ||
+      window.WebKitMutationObserver ||
+      window.MozMutationObserver
+    ;
+
+    if (observer != null) {
+      this._observer = new observer(function (mutations) {
+        $.each(mutations, self._syncA);
+        $.each(mutations, self._syncS);
+      });
+      this._observer.observe(this.$element[0], {
+        attributes: true,
+        childList: true,
+        subtree: false
+      });
+    } else if (this.$element[0].addEventListener) {
+      this.$element[0].addEventListener(
+        'DOMAttrModified',
+        self._syncA,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeInserted',
+        self._syncS,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeRemoved',
+        self._syncS,
+        false
+      );
+    }
+  };
+
+  Select2.prototype._registerDataEvents = function () {
+    var self = this;
+
+    this.dataAdapter.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerSelectionEvents = function () {
+    var self = this;
+    var nonRelayEvents = ['toggle', 'focus'];
+
+    this.selection.on('toggle', function () {
+      self.toggleDropdown();
+    });
+
+    this.selection.on('focus', function (params) {
+      self.focus(params);
+    });
+
+    this.selection.on('*', function (name, params) {
+      if ($.inArray(name, nonRelayEvents) !== -1) {
+        return;
+      }
+
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerDropdownEvents = function () {
+    var self = this;
+
+    this.dropdown.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerResultsEvents = function () {
+    var self = this;
+
+    this.results.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerEvents = function () {
+    var self = this;
+
+    this.on('open', function () {
+      self.$container.addClass('select2-container--open');
+    });
+
+    this.on('close', function () {
+      self.$container.removeClass('select2-container--open');
+    });
+
+    this.on('enable', function () {
+      self.$container.removeClass('select2-container--disabled');
+    });
+
+    this.on('disable', function () {
+      self.$container.addClass('select2-container--disabled');
+    });
+
+    this.on('blur', function () {
+      self.$container.removeClass('select2-container--focus');
+    });
+
+    this.on('query', function (params) {
+      if (!self.isOpen()) {
+        self.trigger('open', {});
+      }
+
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:all', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('query:append', function (params) {
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:append', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('keypress', function (evt) {
+      var key = evt.which;
+
+      if (self.isOpen()) {
+        if (key === KEYS.ESC || key === KEYS.TAB ||
+            (key === KEYS.UP && evt.altKey)) {
+          self.close();
+
+          evt.preventDefault();
+        } else if (key === KEYS.ENTER) {
+          self.trigger('results:select', {});
+
+          evt.preventDefault();
+        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
+          self.trigger('results:toggle', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.UP) {
+          self.trigger('results:previous', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.DOWN) {
+          self.trigger('results:next', {});
+
+          evt.preventDefault();
+        }
+      } else {
+        if (key === KEYS.ENTER || key === KEYS.SPACE ||
+            (key === KEYS.DOWN && evt.altKey)) {
+          self.open();
+
+          evt.preventDefault();
+        }
+      }
+    });
+  };
+
+  Select2.prototype._syncAttributes = function () {
+    this.options.set('disabled', this.$element.prop('disabled'));
+
+    if (this.options.get('disabled')) {
+      if (this.isOpen()) {
+        this.close();
+      }
+
+      this.trigger('disable', {});
+    } else {
+      this.trigger('enable', {});
+    }
+  };
+
+  Select2.prototype._syncSubtree = function (evt, mutations) {
+    var changed = false;
+    var self = this;
+
+    // Ignore any mutation events raised for elements that aren't options or
+    // optgroups. This handles the case when the select element is destroyed
+    if (
+      evt && evt.target && (
+        evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
+      )
+    ) {
+      return;
+    }
+
+    if (!mutations) {
+      // If mutation events aren't supported, then we can only assume that the
+      // change affected the selections
+      changed = true;
+    } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
+      for (var n = 0; n < mutations.addedNodes.length; n++) {
+        var node = mutations.addedNodes[n];
+
+        if (node.selected) {
+          changed = true;
+        }
+      }
+    } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
+      changed = true;
+    }
+
+    // Only re-pull the data if we think there is a change
+    if (changed) {
+      this.dataAdapter.current(function (currentData) {
+        self.trigger('selection:update', {
+          data: currentData
+        });
+      });
+    }
+  };
+
+  /**
+   * Override the trigger method to automatically trigger pre-events when
+   * there are events that can be prevented.
+   */
+  Select2.prototype.trigger = function (name, args) {
+    var actualTrigger = Select2.__super__.trigger;
+    var preTriggerMap = {
+      'open': 'opening',
+      'close': 'closing',
+      'select': 'selecting',
+      'unselect': 'unselecting',
+      'clear': 'clearing'
+    };
+
+    if (args === undefined) {
+      args = {};
+    }
+
+    if (name in preTriggerMap) {
+      var preTriggerName = preTriggerMap[name];
+      var preTriggerArgs = {
+        prevented: false,
+        name: name,
+        args: args
+      };
+
+      actualTrigger.call(this, preTriggerName, preTriggerArgs);
+
+      if (preTriggerArgs.prevented) {
+        args.prevented = true;
+
+        return;
+      }
+    }
+
+    actualTrigger.call(this, name, args);
+  };
+
+  Select2.prototype.toggleDropdown = function () {
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    if (this.isOpen()) {
+      this.close();
+    } else {
+      this.open();
+    }
+  };
+
+  Select2.prototype.open = function () {
+    if (this.isOpen()) {
+      return;
+    }
+
+    this.trigger('query', {});
+  };
+
+  Select2.prototype.close = function () {
+    if (!this.isOpen()) {
+      return;
+    }
+
+    this.trigger('close', {});
+  };
+
+  Select2.prototype.isOpen = function () {
+    return this.$container.hasClass('select2-container--open');
+  };
+
+  Select2.prototype.hasFocus = function () {
+    return this.$container.hasClass('select2-container--focus');
+  };
+
+  Select2.prototype.focus = function (data) {
+    // No need to re-trigger focus events if we are already focused
+    if (this.hasFocus()) {
+      return;
+    }
+
+    this.$container.addClass('select2-container--focus');
+    this.trigger('focus', {});
+  };
+
+  Select2.prototype.enable = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("enable")` method has been deprecated and will' +
+        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
+        ' instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      args = [true];
+    }
+
+    var disabled = !args[0];
+
+    this.$element.prop('disabled', disabled);
+  };
+
+  Select2.prototype.data = function () {
+    if (this.options.get('debug') &&
+        arguments.length > 0 && window.console && console.warn) {
+      console.warn(
+        'Select2: Data can no longer be set using `select2("data")`. You ' +
+        'should consider setting the value instead using `$element.val()`.'
+      );
+    }
+
+    var data = [];
+
+    this.dataAdapter.current(function (currentData) {
+      data = currentData;
+    });
+
+    return data;
+  };
+
+  Select2.prototype.val = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("val")` method has been deprecated and will be' +
+        ' removed in later Select2 versions. Use $element.val() instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      return this.$element.val();
+    }
+
+    var newVal = args[0];
+
+    if ($.isArray(newVal)) {
+      newVal = $.map(newVal, function (obj) {
+        return obj.toString();
+      });
+    }
+
+    this.$element.val(newVal).trigger('change');
+  };
+
+  Select2.prototype.destroy = function () {
+    this.$container.remove();
+
+    if (this.$element[0].detachEvent) {
+      this.$element[0].detachEvent('onpropertychange', this._syncA);
+    }
+
+    if (this._observer != null) {
+      this._observer.disconnect();
+      this._observer = null;
+    } else if (this.$element[0].removeEventListener) {
+      this.$element[0]
+        .removeEventListener('DOMAttrModified', this._syncA, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeInserted', this._syncS, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeRemoved', this._syncS, false);
+    }
+
+    this._syncA = null;
+    this._syncS = null;
+
+    this.$element.off('.select2');
+    this.$element.attr('tabindex',
+    Utils.GetData(this.$element[0], 'old-tabindex'));
+
+    this.$element.removeClass('select2-hidden-accessible');
+    this.$element.attr('aria-hidden', 'false');
+    Utils.RemoveData(this.$element[0]);
+    this.$element.removeData('select2');
+
+    this.dataAdapter.destroy();
+    this.selection.destroy();
+    this.dropdown.destroy();
+    this.results.destroy();
+
+    this.dataAdapter = null;
+    this.selection = null;
+    this.dropdown = null;
+    this.results = null;
+  };
+
+  Select2.prototype.render = function () {
+    var $container = $(
+      '<span class="select2 select2-container">' +
+        '<span class="selection"></span>' +
+        '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
+      '</span>'
+    );
+
+    $container.attr('dir', this.options.get('dir'));
+
+    this.$container = $container;
+
+    this.$container.addClass('select2-container--' + this.options.get('theme'));
+
+    Utils.StoreData($container[0], 'element', this.$element);
+
+    return $container;
+  };
+
+  return Select2;
+});
+
+S2.define('jquery-mousewheel',[
+  'jquery'
+], function ($) {
+  // Used to shim jQuery.mousewheel for non-full builds.
+  return $;
+});
+
+S2.define('jquery.select2',[
+  'jquery',
+  'jquery-mousewheel',
+
+  './select2/core',
+  './select2/defaults',
+  './select2/utils'
+], function ($, _, Select2, Defaults, Utils) {
+  if ($.fn.select2 == null) {
+    // All methods that should return the element
+    var thisMethods = ['open', 'close', 'destroy'];
+
+    $.fn.select2 = function (options) {
+      options = options || {};
+
+      if (typeof options === 'object') {
+        this.each(function () {
+          var instanceOptions = $.extend(true, {}, options);
+
+          var instance = new Select2($(this), instanceOptions);
+        });
+
+        return this;
+      } else if (typeof options === 'string') {
+        var ret;
+        var args = Array.prototype.slice.call(arguments, 1);
+
+        this.each(function () {
+          var instance = Utils.GetData(this, 'select2');
+
+          if (instance == null && window.console && console.error) {
+            console.error(
+              'The select2(\'' + options + '\') method was called on an ' +
+              'element that is not using Select2.'
+            );
+          }
+
+          ret = instance[options].apply(instance, args);
+        });
+
+        // Check if we should be returning `this`
+        if ($.inArray(options, thisMethods) > -1) {
+          return this;
+        }
+
+        return ret;
+      } else {
+        throw new Error('Invalid arguments for Select2: ' + options);
+      }
+    };
+  }
+
+  if ($.fn.select2.defaults == null) {
+    $.fn.select2.defaults = Defaults;
+  }
+
+  return Select2;
+});
+
+  // Return the AMD loader configuration so it can be used outside of this file
+  return {
+    define: S2.define,
+    require: S2.require
+  };
+}());
+
+  // Autoload the jQuery bindings
+  // We know that all of the modules exist above this, so we're safe
+  var select2 = S2.require('jquery.select2');
+
+  // Hold the AMD module references on the jQuery function that was just loaded
+  // This allows Select2 to use the internal loader outside of this file, such
+  // as in the language files.
+  jQuery.fn.select2.amd = S2;
+
+  // Return the Select2 instance for anyone who is importing it.
+  return select2;
+}));
diff --git a/src/main/resources/static/ajax/libs/select2/select2.min.css b/src/main/resources/static/ajax/libs/select2/select2.min.css
new file mode 100644
index 0000000..60d5990
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/select2/select2.min.css
@@ -0,0 +1 @@
+.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}
diff --git a/src/main/resources/static/ajax/libs/select2/select2.min.js b/src/main/resources/static/ajax/libs/select2/select2.min.js
new file mode 100644
index 0000000..9f17a27
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/select2/select2.min.js
@@ -0,0 +1 @@
+/*! Select2 4.0.7 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(b,c){return void 0===c&&(c="undefined"!=typeof window?require("jquery"):require("jquery")(b)),a(c),c}:a(jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return v.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o=b&&b.split("/"),p=t.map,q=p&&p["*"]||{};if(a){for(a=a.split("/"),g=a.length-1,t.nodeIdCompat&&x.test(a[g])&&(a[g]=a[g].replace(x,"")),"."===a[0].charAt(0)&&o&&(n=o.slice(0,o.length-1),a=n.concat(a)),k=0;k<a.length;k++)if("."===(m=a[k]))a.splice(k,1),k-=1;else if(".."===m){if(0===k||1===k&&".."===a[2]||".."===a[k-1])continue;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}if((o||q)&&p){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),o)for(l=o.length;l>0;l-=1)if((e=p[o.slice(0,l).join("/")])&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&q&&q[d]&&(i=q[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=w.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),o.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){r[a]=b}}function j(a){if(e(s,a)){var c=s[a];delete s[a],u[a]=!0,n.apply(b,c)}if(!e(r,a)&&!e(u,a))throw new Error("No "+a);return r[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return a?k(a):[]}function m(a){return function(){return t&&t.config&&t.config[a]||{}}}var n,o,p,q,r={},s={},t={},u={},v=Object.prototype.hasOwnProperty,w=[].slice,x=/\.js$/;p=function(a,b){var c,d=k(a),e=d[0],g=b[1];return a=d[1],e&&(e=f(e,g),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(g)):f(a,g):(a=f(a,g),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},q={require:function(a){return g(a)},exports:function(a){var b=r[a];return void 0!==b?b:r[a]={}},module:function(a){return{id:a,uri:"",exports:r[a],config:m(a)}}},n=function(a,c,d,f){var h,k,m,n,o,t,v,w=[],x=typeof d;if(f=f||a,t=l(f),"undefined"===x||"function"===x){for(c=!c.length&&d.length?["require","exports","module"]:c,o=0;o<c.length;o+=1)if(n=p(c[o],t),"require"===(k=n.f))w[o]=q.require(a);else if("exports"===k)w[o]=q.exports(a),v=!0;else if("module"===k)h=w[o]=q.module(a);else if(e(r,k)||e(s,k)||e(u,k))w[o]=j(k);else{if(!n.p)throw new Error(a+" missing "+k);n.p.load(n.n,g(f,!0),i(k),{}),w[o]=r[k]}m=d?d.apply(r[a],w):void 0,a&&(h&&h.exports!==b&&h.exports!==r[a]?r[a]=h.exports:m===b&&v||(r[a]=m))}else a&&(r[a]=d)},a=c=o=function(a,c,d,e,f){if("string"==typeof a)return q[a]?q[a](c):j(p(a,l(c)).f);if(!a.splice){if(t=a,t.deps&&o(t.deps,t.callback),!c)return;c.splice?(a=c,c=d,d=null):a=b}return c=c||function(){},"function"==typeof d&&(d=e,e=f),e?n(b,a,c,d):setTimeout(function(){n(b,a,c,d)},4),o},o.config=function(a){return o(a)},a._defined=r,d=function(a,b,c){if("string"!=typeof a)throw new Error("See almond README: incorrect module build, no module name");b.splice||(c=b,b=[]),e(r,a)||e(s,a)||(s[a]=[a,b,c])},d.amd={jQuery:!0}}(),b.requirejs=a,b.require=c,b.define=d}}(),b.define("almond",function(){}),b.define("jquery",[],function(){var b=a||$;return null==b&&console&&console.error&&console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page."),b}),b.define("select2/utils",["jquery"],function(a){function b(a){var b=a.prototype,c=[];for(var d in b){"function"==typeof b[d]&&("constructor"!==d&&c.push(d))}return c}var c={};c.Extend=function(a,b){function c(){this.constructor=a}var d={}.hasOwnProperty;for(var e in b)d.call(b,e)&&(a[e]=b[e]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},c.Decorate=function(a,c){function d(){var b=Array.prototype.unshift,d=c.prototype.constructor.length,e=a.prototype.constructor;d>0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h<g.length;h++){var i=g[h];d.prototype[i]=a.prototype[i]}for(var j=(function(a){var b=function(){};a in d.prototype&&(b=d.prototype[a]);var e=c.prototype[a];return function(){return Array.prototype.unshift.call(arguments,b),e.apply(this,arguments)}}),k=0;k<f.length;k++){var l=f[k];d.prototype[l]=j(l)}return d};var d=function(){this.listeners={}};d.prototype.on=function(a,b){this.listeners=this.listeners||{},a in this.listeners?this.listeners[a].push(b):this.listeners[a]=[b]},d.prototype.trigger=function(a){var b=Array.prototype.slice,c=b.call(arguments,1);this.listeners=this.listeners||{},null==c&&(c=[]),0===c.length&&c.push({}),c[0]._type=a,a in this.listeners&&this.invoke(this.listeners[a],b.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},d.prototype.invoke=function(a,b){for(var c=0,d=a.length;c<d;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;c<a;c++){b+=Math.floor(36*Math.random()).toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e<c.length;e++){var f=c[e];f=f.substring(0,1).toLowerCase()+f.substring(1),f in d||(d[f]={}),e==c.length-1&&(d[f]=a[b]),d=d[f]}delete a[b]}}return a},c.hasScroll=function(b,c){var d=a(c),e=c.style.overflowX,f=c.style.overflowY;return(e!==f||"hidden"!==f&&"visible"!==f)&&("scroll"===e||"scroll"===f||(d.innerHeight()<c.scrollHeight||d.innerWidth()<c.scrollWidth))},c.escapeMarkup=function(a){var b={"\\":"&#92;","&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#47;"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c.__cache={};var e=0;return c.GetUniqueElementId=function(a){var b=a.getAttribute("data-select2-id");return null==b&&(a.id?(b=a.id,a.setAttribute("data-select2-id",b)):(a.setAttribute("data-select2-id",++e),b=e.toString())),b},c.StoreData=function(a,b,d){var e=c.GetUniqueElementId(a);c.__cache[e]||(c.__cache[e]={}),c.__cache[e][b]=d},c.GetData=function(b,d){var e=c.GetUniqueElementId(b);return d?c.__cache[e]&&null!=c.__cache[e][d]?c.__cache[e][d]:a(b).data(d):c.__cache[e]},c.RemoveData=function(a){var b=c.GetUniqueElementId(a);null!=c.__cache[b]&&delete c.__cache[b]},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<ul class="select2-results__options" role="tree"></ul>');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('<li role="treeitem" aria-live="assertive" class="select2-results__option"></li>'),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c<a.results.length;c++){var d=a.results[c],e=this.option(d);b.push(e)}this.$results.append(b)},c.prototype.position=function(a,b){b.find(".select2-results").append(a)},c.prototype.sort=function(a){return this.options.get("sorter")(a)},c.prototype.highlightFirstItem=function(){var a=this.$results.find(".select2-results__option[aria-selected]"),b=a.filter("[aria-selected=true]");b.length>0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var c=this;this.data.current(function(d){var e=a.map(d,function(a){return a.id.toString()});c.$results.find(".select2-results__option[aria-selected]").each(function(){var c=a(this),d=b.GetData(this,"data"),f=""+d.id;null!=d.element&&d.element.selected||null==d.element&&a.inArray(f,e)>-1?c.attr("aria-selected","true"):c.attr("aria-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(c){var d=document.createElement("li");d.className="select2-results__option";var e={role:"treeitem","aria-selected":"false"};c.disabled&&(delete e["aria-selected"],e["aria-disabled"]="true"),null==c.id&&delete e["aria-selected"],null!=c._resultId&&(d.id=c._resultId),c.title&&(d.title=c.title),c.children&&(e.role="group",e["aria-label"]=c.text,delete e["aria-selected"]);for(var f in e){var g=e[f];d.setAttribute(f,g)}if(c.children){var h=a(d),i=document.createElement("strong");i.className="select2-results__group";a(i);this.template(c,i);for(var j=[],k=0;k<c.children.length;k++){var l=c.children[k],m=this.option(l);j.push(m)}var n=a("<ul></ul>",{class:"select2-results__options select2-results__options--nested"});n.append(j),h.append(i),h.append(n)}else this.template(c,d);return b.StoreData(d,"data",c),d},c.prototype.bind=function(c,d){var e=this,f=c.id+"-results";this.$results.attr("id",f),c.on("results:all",function(a){e.clear(),e.append(a.data),c.isOpen()&&(e.setClasses(),e.highlightFirstItem())}),c.on("results:append",function(a){e.append(a.data),c.isOpen()&&e.setClasses()}),c.on("query",function(a){e.hideMessages(),e.showLoading(a)}),c.on("select",function(){c.isOpen()&&(e.setClasses(),e.options.get("scrollAfterSelect")&&e.highlightFirstItem())}),c.on("unselect",function(){c.isOpen()&&(e.setClasses(),e.options.get("scrollAfterSelect")&&e.highlightFirstItem())}),c.on("open",function(){e.$results.attr("aria-expanded","true"),e.$results.attr("aria-hidden","false"),e.setClasses(),e.ensureHighlightVisible()}),c.on("close",function(){e.$results.attr("aria-expanded","false"),e.$results.attr("aria-hidden","true"),e.$results.removeAttr("aria-activedescendant")}),c.on("results:toggle",function(){var a=e.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),c.on("results:select",function(){var a=e.getHighlightedResults();if(0!==a.length){var c=b.GetData(a[0],"data");"true"==a.attr("aria-selected")?e.trigger("close",{}):e.trigger("select",{data:c})}}),c.on("results:previous",function(){var a=e.getHighlightedResults(),b=e.$results.find("[aria-selected]"),c=b.index(a);if(!(c<=0)){var d=c-1;0===a.length&&(d=0);var f=b.eq(d);f.trigger("mouseenter");var g=e.$results.offset().top,h=f.offset().top,i=e.$results.scrollTop()+(h-g);0===d?e.$results.scrollTop(0):h-g<0&&e.$results.scrollTop(i)}}),c.on("results:next",function(){var a=e.getHighlightedResults(),b=e.$results.find("[aria-selected]"),c=b.index(a),d=c+1;if(!(d>=b.length)){var f=b.eq(d);f.trigger("mouseenter");var g=e.$results.offset().top+e.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=e.$results.scrollTop()+h-g;0===d?e.$results.scrollTop(0):h>g&&e.$results.scrollTop(i)}}),c.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted")}),c.on("results:message",function(a){e.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=e.$results.scrollTop(),c=e.$results.get(0).scrollHeight-b+a.deltaY,d=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=e.$results.height();d?(e.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(e.$results.scrollTop(e.$results.get(0).scrollHeight-e.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[aria-selected]",function(c){var d=a(this),f=b.GetData(this,"data");if("true"===d.attr("aria-selected"))return void(e.options.get("multiple")?e.trigger("unselect",{originalEvent:c,data:f}):e.trigger("close",{}));e.trigger("select",{originalEvent:c,data:f})}),this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(c){var d=b.GetData(this,"data");e.getHighlightedResults().removeClass("select2-results__option--highlighted"),e.trigger("results:focus",{data:d,element:a(this)})})},c.prototype.getHighlightedResults=function(){return this.$results.find(".select2-results__option--highlighted")},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),c<=2?this.$results.scrollTop(0):(g>this.$results.outerHeight()||g<0)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var c=a('<span class="select2-selection" role="combobox"  aria-haspopup="true" aria-expanded="false"></span>');return this._tabindex=0,null!=b.GetData(this.$element[0],"old-tabindex")?this._tabindex=b.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),c.attr("title",this.$element.attr("title")),c.attr("tabindex",this._tabindex),this.$selection=c,c},d.prototype.bind=function(a,b){var d=this,e=(a.id,a.id+"-results");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),window.setTimeout(function(){d.$selection.focus()},0),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(c){a(document.body).on("mousedown.select2."+c.id,function(c){var d=a(c.target),e=d.closest(".select2");a(".select2.select2-container--open").each(function(){a(this),this!=e[0]&&b.GetData(this,"element").select2("close")})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){b.find(".selection").append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html('<span class="select2-selection__rendered"></span><span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span>'),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()})},e.prototype.clear=function(){var a=this.$selection.find(".select2-selection__rendered");a.empty(),a.removeAttr("title")},e.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},e.prototype.selectionContainer=function(){return a("<span></span>")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.attr("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('<ul class="select2-selection__rendered"></ul>'),a},d.prototype.bind=function(b,e){var f=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){f.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!f.options.get("disabled")){var d=a(this),e=d.parent(),g=c.GetData(e[0],"data");f.trigger("unselect",{originalEvent:b,data:g})}})},d.prototype.clear=function(){var a=this.$selection.find(".select2-selection__rendered");a.empty(),a.removeAttr("title")},d.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},d.prototype.selectionContainer=function(){return a('<li class="select2-selection__choice"><span class="select2-selection__choice__remove" role="presentation">&times;</span></li>')},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d<a.length;d++){var e=a[d],f=this.selectionContainer(),g=this.display(e,f);f.append(g),f.attr("title",e.title||e.text),c.StoreData(f[0],"data",e),b.push(f)}var h=this.$selection.find(".select2-selection__rendered");c.appendMany(h,b)}},d}),b.define("select2/selection/placeholder",["../utils"],function(a){function b(a,b,c){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c)}return b.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},b.prototype.createPlaceholder=function(a,b){var c=this.selectionContainer();return c.html(this.display(b)),c.addClass("select2-selection__placeholder").removeClass("select2-selection__choice"),c},b.prototype.update=function(a,b){var c=1==b.length&&b[0].id!=this.placeholder.id;if(b.length>1||c)return a.call(this,b);this.clear();var d=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(d)},b}),b.define("select2/selection/allowClear",["jquery","../keys","../utils"],function(a,b,c){function d(){}return d.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},d.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var d=this.$selection.find(".select2-selection__clear");if(0!==d.length){b.stopPropagation();var e=c.GetData(d[0],"data"),f=this.$element.val();this.$element.val(this.placeholder.id);var g={data:e};if(this.trigger("clear",g),g.prevented)return void this.$element.val(f);for(var h=0;h<e.length;h++)if(g={data:e[h]},this.trigger("unselect",g),g.prevented)return void this.$element.val(f);this.$element.trigger("change"),this.trigger("toggle",{})}}},d.prototype._handleKeyboardClear=function(a,c,d){d.isOpen()||c.which!=b.DELETE&&c.which!=b.BACKSPACE||this._handleClear(c)},d.prototype.update=function(b,d){if(b.call(this,d),!(this.$selection.find(".select2-selection__placeholder").length>0||0===d.length)){var e=this.options.get("translations").get("removeAllItems"),f=a('<span class="select2-selection__clear" title="'+e()+'">&times;</span>');c.StoreData(f[0],"data",d),this.$selection.find(".select2-selection__rendered").prepend(f)}},d}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('<li class="select2-search select2-search--inline"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="textbox" aria-autocomplete="list" /></li>');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,d,e){var f=this;a.call(this,d,e),d.on("open",function(){f.$search.trigger("focus")}),d.on("close",function(){f.$search.val(""),f.$search.removeAttr("aria-activedescendant"),f.$search.trigger("focus")}),d.on("enable",function(){f.$search.prop("disabled",!1),f._transferTabIndex()}),d.on("disable",function(){f.$search.prop("disabled",!0)}),d.on("focus",function(a){f.$search.trigger("focus")}),d.on("results:focus",function(a){f.$search.attr("aria-activedescendant",a.id)}),this.$selection.on("focusin",".select2-search--inline",function(a){f.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){f._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){if(a.stopPropagation(),f.trigger("keypress",a),f._keyUpPrevented=a.isDefaultPrevented(),a.which===c.BACKSPACE&&""===f.$search.val()){var d=f.$searchContainer.prev(".select2-selection__choice");if(d.length>0){var e=b.GetData(d[0],"data");f.searchRemoveChoice(e),a.preventDefault()}}});var g=document.documentMode,h=g&&g<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){if(h)return void f.$selection.off("input.search input.searchcheck");f.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(h&&"input"===a.type)return void f.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&f.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;if(this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c){this.$element.find("[data-select2-tag]").length?this.$element.focus():this.$search.focus()}},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{a=.75*(this.$search.val().length+1)+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting","clear","clearing"],g=["opening","closing","selecting","unselecting","clearing"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){return{"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Œ":"OE","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","œ":"oe","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ώ":"ω","ς":"σ","’":"'"}}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),null!=c.id?d+="-"+c.id.toString():d+="-"+a.generateChars(4),d},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f<a.length;f++){var g=a[f].id;-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")});else{var d=a.id;this.$element.val(d),this.$element.trigger("change")}},d.prototype.unselect=function(a){var b=this;if(this.$element.prop("multiple")){if(a.selected=!1,c(a.element).is("option"))return a.element.selected=!1,void this.$element.trigger("change");this.current(function(d){for(var e=[],f=0;f<d.length;f++){var g=d[f].id;g!==a.id&&-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")})}},d.prototype.bind=function(a,b){var c=this;this.container=a,a.on("select",function(a){c.select(a.data)}),a.on("unselect",function(a){c.unselect(a.data)})},d.prototype.destroy=function(){this.$element.find("*").each(function(){b.RemoveData(this)})},d.prototype.query=function(a,b){var d=[],e=this;this.$element.children().each(function(){var b=c(this);if(b.is("option")||b.is("optgroup")){var f=e.item(b),g=e.matches(a,f);null!==g&&d.push(g)}}),b({results:d})},d.prototype.addOptions=function(a){b.appendMany(this.$element,a)},d.prototype.option=function(a){var d;a.children?(d=document.createElement("optgroup"),d.label=a.text):(d=document.createElement("option"),void 0!==d.textContent?d.textContent=a.text:d.innerText=a.text),void 0!==a.id&&(d.value=a.id),a.disabled&&(d.disabled=!0),a.selected&&(d.selected=!0),a.title&&(d.title=a.title);var e=c(d),f=this._normalizeItem(a);return f.element=d,b.StoreData(d,"data",f),e},d.prototype.item=function(a){var d={};if(null!=(d=b.GetData(a[0],"data")))return d;if(a.is("option"))d={id:a.val(),text:a.text(),disabled:a.prop("disabled"),selected:a.prop("selected"),title:a.prop("title")};else if(a.is("optgroup")){d={text:a.prop("label"),children:[],title:a.prop("title")};for(var e=a.children("option"),f=[],g=0;g<e.length;g++){var h=c(e[g]),i=this.item(h);f.push(i)}d.children=f}return d=this._normalizeItem(d),d.element=a[0],b.StoreData(a[0],"data",d),d},d.prototype._normalizeItem=function(a){a!==Object(a)&&(a={id:a,text:a}),a=c.extend({},{text:""},a);var b={selected:!1,disabled:!1};return null!=a.id&&(a.id=a.id.toString()),null!=a.text&&(a.text=a.text.toString()),null==a._resultId&&a.id&&null!=this.container&&(a._resultId=this.generateResultId(this.container,a)),c.extend({},b,a)},d.prototype.matches=function(a,b){return this.options.get("matcher")(a,b)},d}),b.define("select2/data/array",["./select","../utils","jquery"],function(a,b,c){function d(a,b){var c=b.get("data")||[];d.__super__.constructor.call(this,a,b),this.addOptions(this.convertToOptions(c))}return b.Extend(d,a),d.prototype.select=function(a){var b=this.$element.find("option").filter(function(b,c){return c.value==a.id.toString()});0===b.length&&(b=this.option(a),this.addOptions(b)),d.__super__.select.call(this,a)},d.prototype.convertToOptions=function(a){function d(a){return function(){return c(this).val()==a.id}}for(var e=this,f=this.$element.find("option"),g=f.map(function(){return e.item(c(this)).id}).get(),h=[],i=0;i<a.length;i++){var j=this._normalizeItem(a[i]);if(c.inArray(j.id,g)>=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){"status"in d&&(0===d.status||"0"===d.status)||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h<e.length;h++){var i=e[h],j=this._normalizeItem(i),k=this.option(j);this.$element.append(k)}}return b.prototype.query=function(a,b,c){function d(a,f){for(var g=a.results,h=0;h<g.length;h++){var i=g[h],j=null!=i.children&&!d({results:i.children},!0);if((i.text||"").toUpperCase()===(b.term||"").toUpperCase()||j)return!f&&(a.data=g,void c(a))}if(f)return!0;var k=e.createTag(b);if(null!=k){var l=e.option(k);l.attr("data-select2-tag",!0),e.addOptions([l]),e.insertTag(g,k)}a.results=g,c(a)}var e=this;if(this._removeOldTags(),null==b.term||null!=b.page)return void a.call(this,b,c);a.call(this,b,d)},b.prototype.createTag=function(b,c){var d=a.trim(c.term);return""===d?null:{id:d,text:d}},b.prototype.insertTag=function(a,b,c){b.unshift(c)},b.prototype._removeOldTags=function(b){this._lastTag;this.$element.find("option[data-select2-tag]").each(function(){this.selected||a(this).remove()})},b}),b.define("select2/data/tokenizer",["jquery"],function(a){function b(a,b,c){var d=c.get("tokenizer");void 0!==d&&(this.tokenizer=d),a.call(this,b,c)}return b.prototype.bind=function(a,b,c){a.call(this,b,c),this.$search=b.dropdown.$search||b.selection.$search||c.find(".select2-search__field")},b.prototype.query=function(b,c,d){function e(b){var c=g._normalizeItem(b);if(!g.$element.find("option").filter(function(){return a(this).val()===c.id}).length){var d=g.option(c);d.attr("data-select2-tag",!0),g._removeOldTags(),g.addOptions([d])}f(c)}function f(a){g.trigger("select",{data:a})}var g=this;c.term=c.term||"";var h=this.tokenizer(c,this.options,e);h.term!==c.term&&(this.$search.length&&(this.$search.val(h.term),this.$search.focus()),c.term=h.term),b.call(this,c,d)},b.prototype.tokenizer=function(b,c,d,e){for(var f=d.get("tokenSeparators")||[],g=c.term,h=0,i=this.createTag||function(a){return{id:a.term,text:a.term}};h<g.length;){var j=g[h];if(-1!==a.inArray(j,f)){var k=g.substr(0,h),l=a.extend({},c,{term:k}),m=i(l);null!=m?(e(m),g=g.substr(h+1)||"",h=0):h++}else h++}return{term:g}},b}),b.define("select2/data/minimumInputLength",[],function(){function a(a,b,c){this.minimumInputLength=c.get("minimumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){if(b.term=b.term||"",b.term.length<this.minimumInputLength)return void this.trigger("results:message",{message:"inputTooShort",args:{minimum:this.minimumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumInputLength",[],function(){function a(a,b,c){this.maximumInputLength=c.get("maximumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){if(b.term=b.term||"",this.maximumInputLength>0&&b.term.length>this.maximumInputLength)return void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;if(d.maximumSelectionLength>0&&f>=d.maximumSelectionLength)return void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}});a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<span class="select2-dropdown"><span class="select2-results"></span></span>');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('<span class="select2-search select2-search--dropdown"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="textbox" /></span>');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val(""),e.$search.blur()}),c.on("focus",function(){c.isOpen()||e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){e.showSearch(a)?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){e.$results.offset().top+e.$results.outerHeight(!1)+50>=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1)&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('<li class="select2-results__option select2-results__option--load-more"role="treeitem" aria-disabled="true"></li>'),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a("<span></span>"),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){b.StoreData(this,"select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(c){var d=b.GetData(this,"select2-scroll-position");a(this).scrollTop(d.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id;this.$container.parents().filter(b.hasScroll).off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.top<f.top-h.height,k=i.bottom>f.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d<b.length;d++){var e=b[d];e.children?c+=a(e.children):c++}return c}function b(a,b,c,d){this.minimumResultsForSearch=c.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),a.call(this,b,c,d)}return b.prototype.showSearch=function(b,c){return!(a(c.data.results)<this.minimumResultsForSearch)&&b.call(this,c)},b}),b.define("select2/dropdown/selectOnClose",["../utils"],function(a){function b(){}return b.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("close",function(a){d._handleSelectOnClose(a)})},b.prototype._handleSelectOnClose=function(b,c){if(c&&null!=c.originalSelect2Event){var d=c.originalSelect2Event;if("select"===d._type||"unselect"===d._type)return}var e=this.getHighlightedResults();if(!(e.length<1)){var f=a.GetData(e[0],"data");null!=f.element&&f.element.selected||null==f.element&&f.selected||this.trigger("select",{data:f})}},b}),b.define("select2/dropdown/closeOnSelect",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("select",function(a){d._selectTriggered(a)}),b.on("unselect",function(a){d._selectTriggered(a)})},a.prototype._selectTriggered=function(a,b){var c=b.originalEvent;c&&(c.ctrlKey||c.metaKey)||this.trigger("close",{originalEvent:c,originalSelect2Event:b})},a}),b.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"无法载入结果"},inputTooLong:function(a){var b=a.input.length-a.maximum,c="请删除 "+b+" 个字符";return 1!=b&&(c+="s"),c},inputTooShort:function(a){return"请再输入至少"+(a.minimum-a.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(a){var b="最多只能"+a.maximum+"个选项";return 1!=a.maximum&&(b+="s"),b},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),b.define("select2/defaults",["jquery","require","./results","./selection/single","./selection/multiple","./selection/placeholder","./selection/allowClear","./selection/search","./selection/eventRelay","./utils","./translation","./diacritics","./data/select","./data/array","./data/ajax","./data/tags","./data/tokenizer","./data/minimumInputLength","./data/maximumInputLength","./data/maximumSelectionLength","./dropdown","./dropdown/search","./dropdown/hidePlaceholder","./dropdown/infiniteScroll","./dropdown/attachBody","./dropdown/minimumResultsForSearch","./dropdown/selectOnClose","./dropdown/closeOnSelect","./i18n/en"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C){function D(){this.reset()}return D.prototype.apply=function(l){if(l=a.extend(!0,{},this.defaults,l),null==l.dataAdapter){if(null!=l.ajax?l.dataAdapter=o:null!=l.data?l.dataAdapter=n:l.dataAdapter=m,l.minimumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),null==l.tokenSeparators&&null==l.tokenizer||(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L<K.length;L++){var M=K[L],N={};try{N=k.loadPath(M)}catch(a){try{M=this.defaults.amdLanguageBase+M,N=k.loadPath(M)}catch(a){l.debug&&window.console&&console.warn&&console.warn('Select2: The language file for "'+M+'" could not be automatically loaded. A fallback will be used instead.');continue}}J.extend(N)}l.translations=J}else{var O=k.loadPath(this.defaults.amdLanguageBase+"en"),P=new k(l.language);P.extend(O),l.translations=P}return l},D.prototype.reset=function(){function b(a){function b(a){return l[a]||a}return a.replace(/[^\u0000-\u007E]/g,b)}function c(d,e){if(""===a.trim(d.term))return e;if(e.children&&e.children.length>0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){null==c(d,e.children[g])&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var h=b(e.text).toUpperCase(),i=b(d.term).toUpperCase();return h.indexOf(i)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,scrollAfterSelect:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"100%"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(!0,this.defaults,f)},new D}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){function c(a,b){return b.toUpperCase()}var e=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),d.GetData(a[0],"select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),d.StoreData(a[0],"data",d.GetData(a[0],"select2Tags")),d.StoreData(a[0],"tags",!0)),d.GetData(a[0],"ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",d.GetData(a[0],"ajaxUrl")),d.StoreData(a[0],"ajax-Url",d.GetData(a[0],"ajaxUrl")));for(var f={},g=0;g<a[0].attributes.length;g++){var h=a[0].attributes[g].name,i="data-";if(h.substr(0,i.length)==i){var j=h.substring(i.length),k=d.GetData(a[0],j);f[j.replace(/-([a-z])/g,c)]=k}}b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset&&(f=b.extend(!0,{},a[0].dataset,f));var l=b.extend(!0,{},d.GetData(a[0]),f);l=d._convertData(l);for(var m in l)b.inArray(m,e)>-1||(b.isPlainObject(this.options[m])?b.extend(this.options[m],l[m]):this.options[m]=l[m]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,d){null!=c.GetData(a[0],"select2")&&c.GetData(a[0],"select2").destroy(),this.$element=a,this.id=this._generateId(a),d=d||{},this.options=new b(d,a),e.__super__.constructor.call(this);var f=a.attr("tabindex")||0;c.StoreData(a[0],"old-tabindex",f),a.attr("tabindex","-1");var g=this.options.get("dataAdapter");this.dataAdapter=new g(a,this.options);var h=this.render();this._placeContainer(h);var i=this.options.get("selectionAdapter");this.selection=new i(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,h);var j=this.options.get("dropdownAdapter");this.dropdown=new j(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,h);var k=this.options.get("resultsAdapter");this.results=new k(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var l=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){l.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),c.StoreData(a[0],"select2",this),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return e<=0?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;h<i;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e<b.addedNodes.length;e++){var f=b.addedNodes[e];f.selected&&(c=!0)}else b.removedNodes&&b.removedNodes.length>0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting",clear:"clearing"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),null!=a&&0!==a.length||(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",c.GetData(this.$element[0],"old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),c.RemoveData(this.$element[0]),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('<span class="select2 select2-container"><span class="selection"></span><span class="dropdown-wrapper" aria-hidden="true"></span></span>');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),c.StoreData(b[0],"element",this.$element),b},e}),b.define("jquery-mousewheel",["jquery"],function(a){return a}),b.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(a,b,c,d,e){if(null==a.fn.select2){var f=["open","close","destroy"];a.fn.select2=function(b){if("object"==typeof(b=b||{}))return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d,g=Array.prototype.slice.call(arguments,1);return this.each(function(){var a=e.GetData(this,"select2");null==a&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2."),d=a[b].apply(a,g)}),a.inArray(b,f)>-1?this:d}throw new Error("Invalid arguments for Select2: "+b)}}return null==a.fn.select2.defaults&&(a.fn.select2.defaults=d),c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,c});
\ No newline at end of file
diff --git a/src/main/resources/static/ajax/libs/typeahead/bootstrap3-typeahead.js b/src/main/resources/static/ajax/libs/typeahead/bootstrap3-typeahead.js
new file mode 100644
index 0000000..b26b84b
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/typeahead/bootstrap3-typeahead.js
@@ -0,0 +1,774 @@
+/* =============================================================
+ * bootstrap3-typeahead.js v4.0.2
+ * https://github.com/bassjobsen/Bootstrap-3-Typeahead
+ * =============================================================
+ * Original written by @mdo and @fat
+ * =============================================================
+ * Copyright 2014 Bass Jobsen @bassjobsen
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * 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.
+ * ============================================================ */
+
+
+(function (root, factory) {
+
+    'use strict';
+
+    // CommonJS module is defined
+    if (typeof module !== 'undefined' && module.exports) {
+        module.exports = factory(require('jquery'));
+    }
+    // AMD module is defined
+    else if (typeof define === 'function' && define.amd) {
+        define(['jquery'], function ($) {
+            return factory($);
+        });
+    } else {
+        factory(root.jQuery);
+    }
+
+}(this, function ($) {
+
+    'use strict';
+    // jshint laxcomma: true
+
+
+    /* TYPEAHEAD PUBLIC CLASS DEFINITION
+     * ================================= */
+
+    var Typeahead = function (element, options) {
+        this.$element = $(element);
+        this.options = $.extend({}, Typeahead.defaults, options);
+        this.matcher = this.options.matcher || this.matcher;
+        this.sorter = this.options.sorter || this.sorter;
+        this.select = this.options.select || this.select;
+        this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true;
+        this.highlighter = this.options.highlighter || this.highlighter;
+        this.render = this.options.render || this.render;
+        this.updater = this.options.updater || this.updater;
+        this.displayText = this.options.displayText || this.displayText;
+        this.itemLink = this.options.itemLink || this.itemLink;
+        this.itemTitle = this.options.itemTitle || this.itemTitle;
+        this.followLinkOnSelect = this.options.followLinkOnSelect || this.followLinkOnSelect;
+        this.source = this.options.source;
+        this.delay = this.options.delay;
+        this.theme = this.options.theme && this.options.themes && this.options.themes[this.options.theme] || Typeahead.defaults.themes[Typeahead.defaults.theme];
+        this.$menu = $(this.options.menu || this.theme.menu);
+        this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null;
+        this.fitToElement = typeof this.options.fitToElement == 'boolean' ? this.options.fitToElement : false;
+        this.shown = false;
+        this.listen();
+        this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' || this.options.showHintOnFocus === 'all' ? this.options.showHintOnFocus : false;
+        this.afterSelect = this.options.afterSelect;
+        this.afterEmptySelect = this.options.afterEmptySelect;
+        this.addItem = false;
+        this.value = this.$element.val() || this.$element.text();
+        this.keyPressed = false;
+        this.focused = this.$element.is(':focus');
+        this.changeInputOnSelect = this.options.changeInputOnSelect || this.changeInputOnSelect;
+        this.changeInputOnMove = this.options.changeInputOnMove || this.changeInputOnMove;
+        this.openLinkInNewTab = this.options.openLinkInNewTab || this.openLinkInNewTab;
+        this.selectOnBlur = this.options.selectOnBlur || this.selectOnBlur;
+        this.showCategoryHeader = this.options.showCategoryHeader || this.showCategoryHeader;
+    };
+
+    Typeahead.prototype = {
+
+        constructor: Typeahead,
+
+
+        setDefault: function (val) {
+            // var val = this.$menu.find('.active').data('value');
+            this.$element.data('active', val);
+            if (this.autoSelect || val) {
+                var newVal = this.updater(val);
+                // Updater can be set to any random functions via "options" parameter in constructor above.
+                // Add null check for cases when updater returns void or undefined.
+                if (!newVal) {
+                    newVal = '';
+                }
+                this.$element
+                    .val(this.displayText(newVal) || newVal)
+                    .text(this.displayText(newVal) || newVal)
+                    .change();
+                this.afterSelect(newVal);
+            }
+            return this.hide();
+        },
+
+        select: function () {
+            var val = this.$menu.find('.active').data('value');
+
+            this.$element.data('active', val);
+            if (this.autoSelect || val) {
+                var newVal = this.updater(val);
+                // Updater can be set to any random functions via "options" parameter in constructor above.
+                // Add null check for cases when updater returns void or undefined.
+                if (!newVal) {
+                    newVal = '';
+                }
+
+                if (this.changeInputOnSelect) {
+                    this.$element
+                        .val(this.displayText(newVal) || newVal)
+                        .text(this.displayText(newVal) || newVal)
+                        .change();
+                }
+
+                if (this.followLinkOnSelect && this.itemLink(val)) {
+                    if (this.openLinkInNewTab) {
+                        window.open(this.itemLink(val), '_blank');
+                    } else {
+                        document.location = this.itemLink(val);
+                    }
+                    this.afterSelect(newVal);
+                } else if (this.followLinkOnSelect && !this.itemLink(val)) {
+                    this.afterEmptySelect(newVal);
+                } else {
+                    this.afterSelect(newVal);
+                }
+            } else {
+                this.afterEmptySelect();
+            }
+
+            return this.hide();
+        },
+
+        updater: function (item) {
+            return item;
+        },
+
+        setSource: function (source) {
+            this.source = source;
+        },
+
+        show: function () {
+            var pos = $.extend({}, this.$element.position(), {
+                height: this.$element[0].offsetHeight
+            });
+
+            var scrollHeight = typeof this.options.scrollHeight == 'function' ?
+                this.options.scrollHeight.call() :
+                this.options.scrollHeight;
+
+            var element;
+            if (this.shown) {
+                element = this.$menu;
+            } else if (this.$appendTo) {
+                element = this.$menu.appendTo(this.$appendTo);
+                this.hasSameParent = this.$appendTo.is(this.$element.parent());
+            } else {
+                element = this.$menu.insertAfter(this.$element);
+                this.hasSameParent = true;
+            }
+
+            if (!this.hasSameParent) {
+                // We cannot rely on the element position, need to position relative to the window
+                element.css('position', 'fixed');
+                var offset = this.$element.offset();
+                pos.top = offset.top;
+                pos.left = offset.left;
+            }
+            // The rules for bootstrap are: 'dropup' in the parent and 'dropdown-menu-right' in the element.
+            // Note that to get right alignment, you'll need to specify `menu` in the options to be:
+            // '<ul class="typeahead dropdown-menu" role="listbox"></ul>'
+            var dropup = $(element).parent().hasClass('dropup');
+            var newTop = dropup ? 'auto' : (pos.top + pos.height + scrollHeight);
+            var right = $(element).hasClass('dropdown-menu-right');
+            var newLeft = right ? 'auto' : pos.left;
+            // it seems like setting the css is a bad idea (just let Bootstrap do it), but I'll keep the old
+            // logic in place except for the dropup/right-align cases.
+            element.css({ top: newTop, left: newLeft }).show();
+
+            if (this.options.fitToElement === true) {
+                element.css('width', this.$element.outerWidth() + 'px');
+            }
+
+            this.shown = true;
+            return this;
+        },
+
+        hide: function () {
+            this.$menu.hide();
+            this.shown = false;
+            return this;
+        },
+
+        lookup: function (query) {
+            if (typeof(query) != 'undefined' && query !== null) {
+                this.query = query;
+            } else {
+                this.query = this.$element.val();
+            }
+
+            if (this.query.length < this.options.minLength && !this.options.showHintOnFocus) {
+                return this.shown ? this.hide() : this;
+            }
+
+            var worker = $.proxy(function () {
+
+                // Bloodhound (since 0.11) needs three arguments.
+                // Two of them are callback functions (sync and async) for local and remote data processing
+                // see https://github.com/twitter/typeahead.js/blob/master/src/bloodhound/bloodhound.js#L132
+                if ($.isFunction(this.source) && this.source.length === 3) {
+                    this.source(this.query, $.proxy(this.process, this), $.proxy(this.process, this));
+                } else if ($.isFunction(this.source)) {
+                    this.source(this.query, $.proxy(this.process, this));
+                } else if (this.source) {
+                    this.process(this.source);
+                }
+            }, this);
+
+            clearTimeout(this.lookupWorker);
+            this.lookupWorker = setTimeout(worker, this.delay);
+        },
+
+        process: function (items) {
+            var that = this;
+
+            items = $.grep(items, function (item) {
+                return that.matcher(item);
+            });
+
+            items = this.sorter(items);
+
+            if (!items.length && !this.options.addItem) {
+                return this.shown ? this.hide() : this;
+            }
+
+            if (items.length > 0) {
+                this.$element.data('active', items[0]);
+            } else {
+                this.$element.data('active', null);
+            }
+
+            if (this.options.items != 'all') {
+                items = items.slice(0, this.options.items);
+            }
+
+            // Add item
+            if (this.options.addItem) {
+                items.push(this.options.addItem);
+            }
+
+            return this.render(items).show();
+        },
+
+        matcher: function (item) {
+            var it = this.displayText(item);
+            return ~it.toLowerCase().indexOf(this.query.toLowerCase());
+        },
+
+        sorter: function (items) {
+            var beginswith = [];
+            var caseSensitive = [];
+            var caseInsensitive = [];
+            var item;
+
+            while ((item = items.shift())) {
+                var it = this.displayText(item);
+                if (!it.toLowerCase().indexOf(this.query.toLowerCase())) {
+                    beginswith.push(item);
+                } else if (~it.indexOf(this.query)) {
+                    caseSensitive.push(item);
+                } else {
+                    caseInsensitive.push(item);
+                }
+            }
+
+            return beginswith.concat(caseSensitive, caseInsensitive);
+        },
+
+        highlighter: function (item) {
+            var text = this.query;
+            if (text === '') {
+                return item;
+            }
+            var matches = item.match(/(>)([^<]*)(<)/g);
+            var first = [];
+            var second = [];
+            var i;
+            if (matches && matches.length) {
+                // html
+                for (i = 0; i < matches.length; ++i) {
+                    if (matches[i].length > 2) {// escape '><'
+                        first.push(matches[i]);
+                    }
+                }
+            } else {
+                // text
+                first = [];
+                first.push(item);
+            }
+            text = text.replace((/[\(\)\/\.\*\+\?\[\]]/g), function (mat) {
+                return '\\' + mat;
+            });
+            var reg = new RegExp(text, 'g');
+            var m;
+            for (i = 0; i < first.length; ++i) {
+                m = first[i].match(reg);
+                if (m && m.length > 0) {// find all text nodes matches
+                    second.push(first[i]);
+                }
+            }
+            for (i = 0; i < second.length; ++i) {
+                item = item.replace(second[i], second[i].replace(reg, '<strong>$&</strong>'));
+            }
+            return item;
+        },
+
+        render: function (items) {
+            var that = this;
+            var self = this;
+            var activeFound = false;
+            var data = [];
+            var _category = that.options.separator;
+
+            $.each(items, function (key, value) {
+                // inject separator
+                if (key > 0 && value[_category] !== items[key - 1][_category]) {
+                    data.push({
+                        __type: 'divider'
+                    });
+                }
+
+                if (this.showCategoryHeader) {
+                    // inject category header
+                    if (value[_category] && (key === 0 || value[_category] !== items[key - 1][_category])) {
+                        data.push({
+                            __type: 'category',
+                            name: value[_category]
+                        });
+                    }
+                }
+
+                data.push(value);
+            });
+
+            items = $(data).map(function (i, item) {
+                    if ((item.__type || false) == 'category'){
+                        return $(that.options.headerHtml || that.theme.headerHtml).text(item.name)[0];
+                    }
+
+                    if ((item.__type || false) == 'divider'){
+                        return $(that.options.headerDivider || that.theme.headerDivider)[0];
+                    }
+
+                    var text = self.displayText(item);
+                    i = $(that.options.item || that.theme.item).data('value', item);
+                    i.find(that.options.itemContentSelector || that.theme.itemContentSelector)
+                        .addBack(that.options.itemContentSelector || that.theme.itemContentSelector)
+                        .html(that.highlighter(text, item));
+                    if(that.options.followLinkOnSelect) {
+                        i.find('a').attr('href', self.itemLink(item));
+                    }
+                    i.find('a').attr('title', self.itemTitle(item));
+                    if (text == self.$element.val()) {
+                        i.addClass('active');
+                        self.$element.data('active', item);
+                        activeFound = true;
+                    }
+                    return i[0];
+                });
+
+            if (this.autoSelect && !activeFound) {
+                items.filter(':not(.dropdown-header)').first().addClass('active');
+                this.$element.data('active', items.first().data('value'));
+            }
+            this.$menu.html(items);
+            return this;
+        },
+
+        displayText: function (item) {
+            return typeof item !== 'undefined' && typeof item.name != 'undefined' ? item.name : item;
+        },
+
+        itemLink: function (item) {
+            return null;
+        },
+
+        itemTitle: function (item) {
+            return null;
+        },
+
+        next: function (event) {
+            var active = this.$menu.find('.active').removeClass('active');
+            var next = active.next();
+
+            if (!next.length) {
+                next = $(this.$menu.find($(this.options.item || this.theme.item).prop('tagName'))[0]);
+            }
+
+            while (next.hasClass('divider') || next.hasClass('dropdown-header')) {
+                next = next.next();
+            }
+
+            next.addClass('active');
+            // added for screen reader
+            var newVal = this.updater(next.data('value'));
+            if (this.changeInputOnMove) {
+                this.$element.val(this.displayText(newVal) || newVal);
+            }
+        },
+
+        prev: function (event) {
+            var active = this.$menu.find('.active').removeClass('active');
+            var prev = active.prev();
+
+            if (!prev.length) {
+                prev = this.$menu.find($(this.options.item || this.theme.item).prop('tagName')).last();
+            }
+
+            while (prev.hasClass('divider') || prev.hasClass('dropdown-header')) {
+                prev = prev.prev();
+            }
+
+            prev.addClass('active');
+            // added for screen reader
+            var newVal = this.updater(prev.data('value'));
+            if (this.changeInputOnMove) {
+                this.$element.val(this.displayText(newVal) || newVal);
+            }
+        },
+
+        listen: function () {
+            this.$element
+                .on('focus.bootstrap3Typeahead', $.proxy(this.focus, this))
+                .on('blur.bootstrap3Typeahead', $.proxy(this.blur, this))
+                .on('keypress.bootstrap3Typeahead', $.proxy(this.keypress, this))
+                .on('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead', $.proxy(this.input, this))
+                .on('keyup.bootstrap3Typeahead', $.proxy(this.keyup, this));
+
+            if (this.eventSupported('keydown')) {
+                this.$element.on('keydown.bootstrap3Typeahead', $.proxy(this.keydown, this));
+            }
+
+            var itemTagName = $(this.options.item || this.theme.item).prop('tagName');
+            if ('ontouchstart' in document.documentElement && 'onmousemove' in document.documentElement) {
+		        this.$menu
+		            .on('touchstart', itemTagName, $.proxy(this.touchstart, this))
+		            .on('touchend', itemTagName, $.proxy(this.click, this))
+		            .on('click', $.proxy(this.click, this))
+		            .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this))
+		            .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this))
+		            .on('mousedown', $.proxy(this.mousedown,this));
+	        } else if ('ontouchstart' in document.documentElement) {
+		        this.$menu
+		            .on('touchstart', itemTagName, $.proxy(this.touchstart, this))
+		            .on('touchend', itemTagName, $.proxy(this.click, this));
+	        } else {
+                this.$menu
+                    .on('click', $.proxy(this.click, this))
+                    .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this))
+                    .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this))
+                    .on('mousedown', $.proxy(this.mousedown, this));
+            }
+        },
+
+        destroy: function () {
+            this.$element.data('typeahead', null);
+            this.$element.data('active', null);
+            this.$element
+                .unbind('focus.bootstrap3Typeahead')
+                .unbind('blur.bootstrap3Typeahead')
+                .unbind('keypress.bootstrap3Typeahead')
+                .unbind('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead')
+                .unbind('keyup.bootstrap3Typeahead');
+
+            if (this.eventSupported('keydown')) {
+                this.$element.unbind('keydown.bootstrap3-typeahead');
+            }
+
+            this.$menu.remove();
+            this.destroyed = true;
+        },
+
+        eventSupported: function (eventName) {
+            var isSupported = eventName in this.$element;
+            if (!isSupported) {
+                this.$element.setAttribute(eventName, 'return;');
+                isSupported = typeof this.$element[eventName] === 'function';
+            }
+            return isSupported;
+        },
+
+        move: function (e) {
+            if (!this.shown) {
+                return;
+            }
+
+            switch (e.keyCode) {
+                case 9: // tab
+                case 13: // enter
+                case 27: // escape
+                    e.preventDefault();
+                    break;
+
+                case 38: // up arrow
+                    // with the shiftKey (this is actually the left parenthesis)
+                    if (e.shiftKey) {
+                        return;
+                    }
+                    e.preventDefault();
+                    this.prev();
+                    break;
+
+                case 40: // down arrow
+                    // with the shiftKey (this is actually the right parenthesis)
+                    if (e.shiftKey) {
+                        return;
+                    }
+                    e.preventDefault();
+                    this.next();
+                    break;
+            }
+        },
+
+        keydown: function (e) {
+            /**
+             * Prevent to make an ajax call while copying and pasting.
+             *
+             * @author Simone Sacchi
+             * @version 2018/01/18
+             */
+            if (e.keyCode === 17) { // ctrl
+                return;
+            }
+            this.keyPressed = true;
+            this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40, 38, 9, 13, 27]);
+            if (!this.shown && e.keyCode == 40) {
+                this.lookup();
+            } else {
+                this.move(e);
+            }
+        },
+
+        keypress: function (e) {
+            if (this.suppressKeyPressRepeat) {
+                return;
+            }
+            this.move(e);
+        },
+
+        input: function (e) {
+            // This is a fixed for IE10/11 that fires the input event when a placehoder is changed
+            // (https://connect.microsoft.com/IE/feedback/details/810538/ie-11-fires-input-event-on-focus)
+            var currentValue = this.$element.val() || this.$element.text();
+            if (this.value !== currentValue) {
+                this.value = currentValue;
+                this.lookup();
+            }
+        },
+
+        keyup: function (e) {
+            if (this.destroyed) {
+                return;
+            }
+            switch (e.keyCode) {
+                case 40: // down arrow
+                case 38: // up arrow
+                case 16: // shift
+                case 17: // ctrl
+                case 18: // alt
+                    break;
+
+                case 9: // tab
+                    if (!this.shown || (this.showHintOnFocus && !this.keyPressed)) {
+                        return;
+                    }
+                    this.select();
+                    break;
+                case 13: // enter
+                    if (!this.shown) {
+                        return;
+                    }
+                    this.select();
+                    break;
+
+                case 27: // escape
+                    if (!this.shown) {
+                        return;
+                    }
+                    this.hide();
+                    break;
+            }
+
+        },
+
+        focus: function (e) {
+            if (!this.focused) {
+                this.focused = true;
+                this.keyPressed = false;
+                if (this.options.showHintOnFocus && this.skipShowHintOnFocus !== true) {
+                    if (this.options.showHintOnFocus === 'all') {
+                        this.lookup('');
+                    } else {
+                        this.lookup();
+                    }
+                }
+            }
+            if (this.skipShowHintOnFocus) {
+                this.skipShowHintOnFocus = false;
+            }
+        },
+
+        blur: function (e) {
+            if (!this.mousedover && !this.mouseddown && this.shown) {
+                if (this.selectOnBlur) {
+                    this.select();
+                }
+                this.hide();
+                this.focused = false;
+                this.keyPressed = false;
+            } else if (this.mouseddown) {
+                // This is for IE that blurs the input when user clicks on scroll.
+                // We set the focus back on the input and prevent the lookup to occur again
+                this.skipShowHintOnFocus = true;
+                this.$element.focus();
+                this.mouseddown = false;
+            }
+        },
+
+        click: function (e) {
+            e.preventDefault();
+            this.skipShowHintOnFocus = true;
+            this.select();
+            this.$element.focus();
+            this.hide();
+        },
+
+        mouseenter: function (e) {
+            this.mousedover = true;
+            this.$menu.find('.active').removeClass('active');
+            $(e.currentTarget).addClass('active');
+        },
+
+        mouseleave: function (e) {
+            this.mousedover = false;
+            if (!this.focused && this.shown) {
+                this.hide();
+            }
+        },
+
+        /**
+         * We track the mousedown for IE. When clicking on the menu scrollbar, IE makes the input blur thus hiding the menu.
+         */
+        mousedown: function (e) {
+            this.mouseddown = true;
+            this.$menu.one('mouseup', function (e) {
+                // IE won't fire this, but FF and Chrome will so we reset our flag for them here
+                this.mouseddown = false;
+            }.bind(this));
+        },
+
+        touchstart: function (e) {
+            e.preventDefault();
+            this.$menu.find('.active').removeClass('active');
+            $(e.currentTarget).addClass('active');
+        },
+
+        touchend: function (e) {
+            e.preventDefault();
+            this.select();
+            this.$element.focus();
+        }
+
+    };
+
+
+    /* TYPEAHEAD PLUGIN DEFINITION
+     * =========================== */
+
+    var old = $.fn.typeahead;
+
+    $.fn.typeahead = function (option) {
+        var arg = arguments;
+        if (typeof option == 'string' && option == 'getActive') {
+            return this.data('active');
+        }
+        return this.each(function () {
+            var $this = $(this);
+            var data = $this.data('typeahead');
+            var options = typeof option == 'object' && option;
+            if (!data) {
+                $this.data('typeahead', (data = new Typeahead(this, options)));
+            }
+            if (typeof option == 'string' && data[option]) {
+                if (arg.length > 1) {
+                    data[option].apply(data, Array.prototype.slice.call(arg, 1));
+                } else {
+                    data[option]();
+                }
+            }
+        });
+    };
+
+    Typeahead.defaults = {
+        source: [],
+        items: 8,
+        minLength: 1,
+        scrollHeight: 0,
+        autoSelect: true,
+        afterSelect: $.noop,
+        afterEmptySelect: $.noop,
+        addItem: false,
+        followLinkOnSelect: false,
+        delay: 0,
+        separator: 'category',
+        changeInputOnSelect: true,
+        changeInputOnMove: true,
+        openLinkInNewTab: false,
+        selectOnBlur: true,
+        showCategoryHeader: true,
+        theme: "bootstrap3",
+        themes: {
+        bootstrap3: {
+            menu: '<ul class="typeahead dropdown-menu" role="listbox"></ul>',
+            item: '<li><a class="dropdown-item" href="#" role="option"></a></li>',
+            itemContentSelector: "a",
+            headerHtml: '<li class="dropdown-header"></li>',
+            headerDivider: '<li class="divider" role="separator"></li>'
+        },
+        bootstrap4: {
+            menu: '<div class="typeahead dropdown-menu" role="listbox"></div>',
+            item: '<button class="dropdown-item" role="option"></button>',
+            itemContentSelector: '.dropdown-item',
+            headerHtml: '<h6 class="dropdown-header"></h6>',
+            headerDivider: '<div class="dropdown-divider"></div>'
+        }
+    }
+};
+
+    $.fn.typeahead.Constructor = Typeahead;
+
+    /* TYPEAHEAD NO CONFLICT
+     * =================== */
+
+    $.fn.typeahead.noConflict = function () {
+        $.fn.typeahead = old;
+        return this;
+    };
+
+
+    /* TYPEAHEAD DATA-API
+     * ================== */
+
+    $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+        var $this = $(this);
+        if ($this.data('typeahead')) {
+            return;
+        }
+        $this.typeahead($this.data());
+    });
+
+}));
diff --git a/src/main/resources/static/ajax/libs/typeahead/bootstrap3-typeahead.min.js b/src/main/resources/static/ajax/libs/typeahead/bootstrap3-typeahead.min.js
new file mode 100644
index 0000000..f184d08
--- /dev/null
+++ b/src/main/resources/static/ajax/libs/typeahead/bootstrap3-typeahead.min.js
@@ -0,0 +1 @@
+!function(t,e){"use strict";"undefined"!=typeof module&&module.exports?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],function(t){return e(t)}):e(t.jQuery)}(this,function(t){"use strict";var e=function(s,i){this.$element=t(s),this.options=t.extend({},e.defaults,i),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.select=this.options.select||this.select,this.autoSelect="boolean"!=typeof this.options.autoSelect||this.options.autoSelect,this.highlighter=this.options.highlighter||this.highlighter,this.render=this.options.render||this.render,this.updater=this.options.updater||this.updater,this.displayText=this.options.displayText||this.displayText,this.itemLink=this.options.itemLink||this.itemLink,this.itemTitle=this.options.itemTitle||this.itemTitle,this.followLinkOnSelect=this.options.followLinkOnSelect||this.followLinkOnSelect,this.source=this.options.source,this.delay=this.options.delay,this.theme=this.options.theme&&this.options.themes&&this.options.themes[this.options.theme]||e.defaults.themes[e.defaults.theme],this.$menu=t(this.options.menu||this.theme.menu),this.$appendTo=this.options.appendTo?t(this.options.appendTo):null,this.fitToElement="boolean"==typeof this.options.fitToElement&&this.options.fitToElement,this.shown=!1,this.listen(),this.showHintOnFocus=("boolean"==typeof this.options.showHintOnFocus||"all"===this.options.showHintOnFocus)&&this.options.showHintOnFocus,this.afterSelect=this.options.afterSelect,this.afterEmptySelect=this.options.afterEmptySelect,this.addItem=!1,this.value=this.$element.val()||this.$element.text(),this.keyPressed=!1,this.focused=this.$element.is(":focus"),this.changeInputOnSelect=this.options.changeInputOnSelect||this.changeInputOnSelect,this.changeInputOnMove=this.options.changeInputOnMove||this.changeInputOnMove,this.openLinkInNewTab=this.options.openLinkInNewTab||this.openLinkInNewTab,this.selectOnBlur=this.options.selectOnBlur||this.selectOnBlur,this.showCategoryHeader=this.options.showCategoryHeader||this.showCategoryHeader};e.prototype={constructor:e,setDefault:function(t){if(this.$element.data("active",t),this.autoSelect||t){var e=this.updater(t);e||(e=""),this.$element.val(this.displayText(e)||e).text(this.displayText(e)||e).change(),this.afterSelect(e)}return this.hide()},select:function(){var t=this.$menu.find(".active").data("value");if(this.$element.data("active",t),this.autoSelect||t){var e=this.updater(t);e||(e=""),this.changeInputOnSelect&&this.$element.val(this.displayText(e)||e).text(this.displayText(e)||e).change(),this.followLinkOnSelect&&this.itemLink(t)?(this.openLinkInNewTab?window.open(this.itemLink(t),"_blank"):document.location=this.itemLink(t),this.afterSelect(e)):this.followLinkOnSelect&&!this.itemLink(t)?this.afterEmptySelect(e):this.afterSelect(e)}else this.afterEmptySelect();return this.hide()},updater:function(t){return t},setSource:function(t){this.source=t},show:function(){var e,s=t.extend({},this.$element.position(),{height:this.$element[0].offsetHeight}),i="function"==typeof this.options.scrollHeight?this.options.scrollHeight.call():this.options.scrollHeight;if(this.shown?e=this.$menu:this.$appendTo?(e=this.$menu.appendTo(this.$appendTo),this.hasSameParent=this.$appendTo.is(this.$element.parent())):(e=this.$menu.insertAfter(this.$element),this.hasSameParent=!0),!this.hasSameParent){e.css("position","fixed");var o=this.$element.offset();s.top=o.top,s.left=o.left}var n=t(e).parent().hasClass("dropup")?"auto":s.top+s.height+i,h=t(e).hasClass("dropdown-menu-right")?"auto":s.left;return e.css({top:n,left:h}).show(),!0===this.options.fitToElement&&e.css("width",this.$element.outerWidth()+"px"),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(e){if(this.query=null!=e?e:this.$element.val(),this.query.length<this.options.minLength&&!this.options.showHintOnFocus)return this.shown?this.hide():this;var s=t.proxy(function(){t.isFunction(this.source)&&3===this.source.length?this.source(this.query,t.proxy(this.process,this),t.proxy(this.process,this)):t.isFunction(this.source)?this.source(this.query,t.proxy(this.process,this)):this.source&&this.process(this.source)},this);clearTimeout(this.lookupWorker),this.lookupWorker=setTimeout(s,this.delay)},process:function(e){var s=this;return e=t.grep(e,function(t){return s.matcher(t)}),(e=this.sorter(e)).length||this.options.addItem?(e.length>0?this.$element.data("active",e[0]):this.$element.data("active",null),"all"!=this.options.items&&(e=e.slice(0,this.options.items)),this.options.addItem&&e.push(this.options.addItem),this.render(e).show()):this.shown?this.hide():this},matcher:function(t){return~this.displayText(t).toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(t){for(var e,s=[],i=[],o=[];e=t.shift();){var n=this.displayText(e);n.toLowerCase().indexOf(this.query.toLowerCase())?~n.indexOf(this.query)?i.push(e):o.push(e):s.push(e)}return s.concat(i,o)},highlighter:function(t){var e=this.query;if(""===e)return t;var s,i=t.match(/(>)([^<]*)(<)/g),o=[],n=[];if(i&&i.length)for(s=0;s<i.length;++s)i[s].length>2&&o.push(i[s]);else(o=[]).push(t);e=e.replace(/[\(\)\/\.\*\+\?\[\]]/g,function(t){return"\\"+t});var h,a=new RegExp(e,"g");for(s=0;s<o.length;++s)(h=o[s].match(a))&&h.length>0&&n.push(o[s]);for(s=0;s<n.length;++s)t=t.replace(n[s],n[s].replace(a,"<strong>$&</strong>"));return t},render:function(e){var s=this,i=this,o=!1,n=[],h=s.options.separator;return t.each(e,function(t,s){t>0&&s[h]!==e[t-1][h]&&n.push({__type:"divider"}),this.showCategoryHeader&&(!s[h]||0!==t&&s[h]===e[t-1][h]||n.push({__type:"category",name:s[h]})),n.push(s)}),e=t(n).map(function(e,n){if("category"==(n.__type||!1))return t(s.options.headerHtml||s.theme.headerHtml).text(n.name)[0];if("divider"==(n.__type||!1))return t(s.options.headerDivider||s.theme.headerDivider)[0];var h=i.displayText(n);return(e=t(s.options.item||s.theme.item).data("value",n)).find(s.options.itemContentSelector||s.theme.itemContentSelector).addBack(s.options.itemContentSelector||s.theme.itemContentSelector).html(s.highlighter(h,n)),s.options.followLinkOnSelect&&e.find("a").attr("href",i.itemLink(n)),e.find("a").attr("title",i.itemTitle(n)),h==i.$element.val()&&(e.addClass("active"),i.$element.data("active",n),o=!0),e[0]}),this.autoSelect&&!o&&(e.filter(":not(.dropdown-header)").first().addClass("active"),this.$element.data("active",e.first().data("value"))),this.$menu.html(e),this},displayText:function(t){return void 0!==t&&void 0!==t.name?t.name:t},itemLink:function(t){return null},itemTitle:function(t){return null},next:function(e){var s=this.$menu.find(".active").removeClass("active").next();for(s.length||(s=t(this.$menu.find(t(this.options.item||this.theme.item).prop("tagName"))[0]));s.hasClass("divider")||s.hasClass("dropdown-header");)s=s.next();s.addClass("active");var i=this.updater(s.data("value"));this.changeInputOnMove&&this.$element.val(this.displayText(i)||i)},prev:function(e){var s=this.$menu.find(".active").removeClass("active").prev();for(s.length||(s=this.$menu.find(t(this.options.item||this.theme.item).prop("tagName")).last());s.hasClass("divider")||s.hasClass("dropdown-header");)s=s.prev();s.addClass("active");var i=this.updater(s.data("value"));this.changeInputOnMove&&this.$element.val(this.displayText(i)||i)},listen:function(){this.$element.on("focus.bootstrap3Typeahead",t.proxy(this.focus,this)).on("blur.bootstrap3Typeahead",t.proxy(this.blur,this)).on("keypress.bootstrap3Typeahead",t.proxy(this.keypress,this)).on("propertychange.bootstrap3Typeahead input.bootstrap3Typeahead",t.proxy(this.input,this)).on("keyup.bootstrap3Typeahead",t.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown.bootstrap3Typeahead",t.proxy(this.keydown,this));var e=t(this.options.item||this.theme.item).prop("tagName");"ontouchstart"in document.documentElement?this.$menu.on("touchstart",e,t.proxy(this.touchstart,this)).on("touchend",e,t.proxy(this.click,this)):this.$menu.on("click",t.proxy(this.click,this)).on("mouseenter",e,t.proxy(this.mouseenter,this)).on("mouseleave",e,t.proxy(this.mouseleave,this)).on("mousedown",t.proxy(this.mousedown,this))},destroy:function(){this.$element.data("typeahead",null),this.$element.data("active",null),this.$element.unbind("focus.bootstrap3Typeahead").unbind("blur.bootstrap3Typeahead").unbind("keypress.bootstrap3Typeahead").unbind("propertychange.bootstrap3Typeahead input.bootstrap3Typeahead").unbind("keyup.bootstrap3Typeahead"),this.eventSupported("keydown")&&this.$element.unbind("keydown.bootstrap3-typeahead"),this.$menu.remove(),this.destroyed=!0},eventSupported:function(t){var e=t in this.$element;return e||(this.$element.setAttribute(t,"return;"),e="function"==typeof this.$element[t]),e},move:function(t){if(this.shown)switch(t.keyCode){case 9:case 13:case 27:t.preventDefault();break;case 38:if(t.shiftKey)return;t.preventDefault(),this.prev();break;case 40:if(t.shiftKey)return;t.preventDefault(),this.next()}},keydown:function(e){17!==e.keyCode&&(this.keyPressed=!0,this.suppressKeyPressRepeat=~t.inArray(e.keyCode,[40,38,9,13,27]),this.shown||40!=e.keyCode?this.move(e):this.lookup())},keypress:function(t){this.suppressKeyPressRepeat||this.move(t)},input:function(t){var e=this.$element.val()||this.$element.text();this.value!==e&&(this.value=e,this.lookup())},keyup:function(t){if(!this.destroyed)switch(t.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:if(!this.shown||this.showHintOnFocus&&!this.keyPressed)return;this.select();break;case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide()}},focus:function(t){this.focused||(this.focused=!0,this.keyPressed=!1,this.options.showHintOnFocus&&!0!==this.skipShowHintOnFocus&&("all"===this.options.showHintOnFocus?this.lookup(""):this.lookup())),this.skipShowHintOnFocus&&(this.skipShowHintOnFocus=!1)},blur:function(t){this.mousedover||this.mouseddown||!this.shown?this.mouseddown&&(this.skipShowHintOnFocus=!0,this.$element.focus(),this.mouseddown=!1):(this.selectOnBlur&&this.select(),this.hide(),this.focused=!1,this.keyPressed=!1)},click:function(t){t.preventDefault(),this.skipShowHintOnFocus=!0,this.select(),this.$element.focus(),this.hide()},mouseenter:function(e){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),t(e.currentTarget).addClass("active")},mouseleave:function(t){this.mousedover=!1,!this.focused&&this.shown&&this.hide()},mousedown:function(t){this.mouseddown=!0,this.$menu.one("mouseup",function(t){this.mouseddown=!1}.bind(this))},touchstart:function(e){e.preventDefault(),this.$menu.find(".active").removeClass("active"),t(e.currentTarget).addClass("active")},touchend:function(t){t.preventDefault(),this.select(),this.$element.focus()}};var s=t.fn.typeahead;t.fn.typeahead=function(s){var i=arguments;return"string"==typeof s&&"getActive"==s?this.data("active"):this.each(function(){var o=t(this),n=o.data("typeahead"),h="object"==typeof s&&s;n||o.data("typeahead",n=new e(this,h)),"string"==typeof s&&n[s]&&(i.length>1?n[s].apply(n,Array.prototype.slice.call(i,1)):n[s]())})},e.defaults={source:[],items:8,minLength:1,scrollHeight:0,autoSelect:!0,afterSelect:t.noop,afterEmptySelect:t.noop,addItem:!1,followLinkOnSelect:!1,delay:0,separator:"category",changeInputOnSelect:!0,changeInputOnMove:!0,openLinkInNewTab:!1,selectOnBlur:!0,showCategoryHeader:!0,theme:"bootstrap3",themes:{bootstrap3:{menu:'<ul class="typeahead dropdown-menu" role="listbox"></ul>',item:'<li><a class="dropdown-item" href="#" role="option"></a></li>',itemContentSelector:"a",headerHtml:'<li class="dropdown-header"></li>',headerDivider:'<li class="divider" role="separator"></li>'},bootstrap4:{menu:'<div class="typeahead dropdown-menu" role="listbox"></div>',item:'<button class="dropdown-item" role="option"></button>',itemContentSelector:".dropdown-item",headerHtml:'<h6 class="dropdown-header"></h6>',headerDivider:'<div class="dropdown-divider"></div>'}}},t.fn.typeahead.Constructor=e,t.fn.typeahead.noConflict=function(){return t.fn.typeahead=s,this},t(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var s=t(this);s.data("typeahead")||s.typeahead(s.data())})});
\ No newline at end of file
diff --git a/src/main/resources/static/huaheng/css/huahengUI.css b/src/main/resources/static/huaheng/css/huahengUI.css
index 922c67c..837fc8c 100644
--- a/src/main/resources/static/huaheng/css/huahengUI.css
+++ b/src/main/resources/static/huaheng/css/huahengUI.css
@@ -360,3 +360,732 @@ label{
 	width: 49%;
 	float: right;
 }
+
+/** 复选框&单选框  **/
+.check-box,.radio-box {
+	display: inline-block;
+	box-sizing: border-box;
+	cursor: pointer;
+	position: relative;
+	padding-left: 25px;
+	padding-right: 15px;
+	padding-top: 8px;
+}
+
+.icheckbox, .icheckbox-blue, .iradio, .iradio-blue, .iradio-purple {
+	position: absolute;
+	top: 8px;
+	left: 0
+}
+
+/** iCheck **/
+.icheckbox-blue,.iradio-blue {
+	display: block;
+	margin: 0;
+	padding: 0;
+	width: 18px;
+	height: 18px;
+	background: url(../../img/blue.png) no-repeat;
+	border: none;
+	cursor: pointer
+}
+
+.icheckbox-blue,.icheckbox-blue.static:hover {
+	background-position: 0 0
+}
+
+.icheckbox-blue.hover,.icheckbox-blue:hover {
+	background-position: -20px 0
+}
+
+.icheckbox-blue.checked {
+	background-position: -40px 0
+}
+
+.icheckbox-blue.disabled {
+	background-position: -60px 0;
+	cursor: default
+}
+
+.icheckbox-blue.checked.disabled {
+	background-position: -80px 0
+}
+
+.iradio-blue,.iradio-blue.static:hover {
+	background-position: -100px 0
+}
+
+.iradio-blue.hover,.iradio-blue:hover {
+	background-position: -120px 0
+}
+
+.iradio-blue.checked {
+	background-position: -140px 0
+}
+
+.iradio-blue.disabled {
+	background-position: -160px 0;
+	cursor: default
+}
+
+.iradio-blue.checked.disabled {
+	background-position: -180px 0
+}
+
+/* 切换开关 */
+.toggle-switch {
+	display: -webkit-inline-box;
+	display: -webkit-inline-flex;
+	display: -ms-inline-flexbox;
+	display: inline-flex;
+	-webkit-box-align: center;
+	-webkit-align-items: center;
+	-ms-flex-align: center;
+	align-items: center;
+	margin-bottom: 0;
+	padding-top: 8px;
+}
+.toggle-switch input {
+	height: 0;
+	width: 0;
+	position: absolute;
+	opacity: 0;
+}
+.toggle-switch span {
+	display: inline-block;
+	position: relative;
+	width: 40px;
+	height: 10px;
+	-webkit-border-radius: 10px;
+	border-radius: 10px;
+	background-color: #ebebeb;
+	border: 2px solid #ebebeb;
+	cursor: pointer;
+	-webkit-transition: all .1s ease;
+	-o-transition: all .1s ease;
+	transition: all .1s ease
+}
+.toggle-switch span:after {
+	content: '';
+	height: 20px;
+	width: 20px;
+	-webkit-border-radius: 50%;
+	border-radius: 50%;
+	position: absolute;
+	left: 1px;
+	top: -7px;
+	color: #aaa;
+	-webkit-transition: all .1s ease;
+	-o-transition: all .1s ease;
+	transition: all .1s ease;
+	text-align: center;
+	font-size: 13px;
+	background-color: #fff;
+	-webkit-box-shadow: rgba(0,0,0,.12) 0 1px 6px,rgba(0,0,0,.12) 0 1px 4px;
+	box-shadow: rgba(0,0,0,.12) 0 1px 6px,rgba(0,0,0,.12) 0 1px 4px
+}
+.toggle-switch input:checked~span:after {
+	left: -webkit-calc(100% - 20px);
+	left: calc(100% - 20px);
+	background-color: #33cabb
+}
+
+.toggle-switch.switch-solid span  {
+	height: 20px;
+}
+.toggle-switch.switch-solid span:after {
+	top: -2px;
+}
+.switch-solid input:checked~span {
+	background-color: #33cabb;
+	border-color: #33cabb
+}
+.switch-solid input:checked~span:after {
+	background-color: #fff;
+	color: #33cabb
+}
+
+/** 遮罩层 **/
+.loaderbox {
+	display: inline-block;
+	min-width: 125px;
+	padding: 10px;
+	margin: 0 auto;
+	color: #000 !important;
+	font-size: 13px;
+	font-weight: 400;
+	text-align: center;
+	vertical-align: middle;
+	border: 1px solid #ddd;
+	background-color: #eee;
+	-webkit-border-radius: 2px;
+	-moz-border-radius: 2px;
+	-ms-border-radius: 2px;
+	-o-border-radius: 2px;
+	border-radius: 2px;
+	-webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1);
+	-moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1);
+	box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1);
+}
+
+.loaderbox .loading-activity {
+	float: left;
+	width: 18px;
+	height: 18px;
+	border: solid 2px transparent;
+	border-top-color: #000;
+	border-left-color: #000;
+	border-radius: 10px;
+	-webkit-animation: pace-spinner 400ms linear infinite;
+	-moz-animation: pace-spinner 400ms linear infinite;
+	-ms-animation: pace-spinner 400ms linear infinite;
+	-o-animation: pace-spinner 400ms linear infinite;
+	animation: pace-spinner 400ms linear infinite;
+}
+
+@media (max-width: 767px) {
+	.loading-activity {
+		width: 18px;
+		height: 18px;
+	}
+}
+
+@-ms-keyframes pace-spinner {
+	0% {
+		-ms-transform: rotate(0deg);
+		transform: rotate(0deg);
+	}
+
+	100% {
+		-ms-transform: rotate(360deg);
+		transform: rotate(360deg);
+	}
+}
+
+@keyframes pace-spinner {
+	0% {
+		transform: rotate(0deg);
+	}
+
+	100% {
+		transform: rotate(360deg);
+	}
+}
+
+/** 表单查询条件  **/
+.select-list ul, .layui-layer-content ul {
+	margin: 0;
+	padding: 0;
+	-webkit-tap-highlight-color: rgba(0,0,0,0);
+}
+
+.select-list li, .layui-layer-content li {
+	list-style: none;
+}
+
+.time-input {
+	display: block;
+	width: 100%;
+	padding-left: 10px;
+}
+
+label {
+	font-weight: normal;
+}
+
+.container-div {
+	padding: 0px 28px;
+	height: 100%;
+}
+
+.container-div .row {
+	height: 100%;
+}
+
+.search-collapse,.select-table {
+	width: 100%;
+	background: #fff;
+	border-radius: 6px;
+	margin-top: 10px;
+	padding-top: 5px;
+	padding-bottom: 13px;
+	box-shadow: 1px 1px 3px rgba(0,0,0,.2);
+}
+
+.search-collapse {
+	position: relative;
+}
+
+.search-collapse .col-sm-6 .control-label {
+	color: #333;
+}
+
+@media ( max-width : 768px) {
+	.search-collapse {
+		display: none;
+	}
+}
+
+@media ( min-width : 768px) {
+	.select-list li {
+		float: left;
+	}
+}
+
+.select-list li {
+	color: #333;
+	margin: 5px 15px 5px 0px;
+}
+
+.select-list li p, .select-list li label:not(.radio-box){
+	float: left;
+	width: 65px;
+	margin: 5px 0px 0px 0px;
+	text-align:right;
+}
+
+.select-list li input {
+	border: 1px solid #ddd;
+	border-radius: 4px;
+	background: transparent;
+	outline: none;
+	height: 30px;
+	width: 200px;
+	padding-left: 5px;
+}
+
+.select-list li .submit-btn {
+	border: 0px;
+	border-radius: 4px;
+	background: transparent;
+	outline: none;
+	width: 40px;
+	height: 23px;
+}
+
+.select-list li select {
+	border: 1px solid #ddd;
+	border-radius: 4px;
+	background: transparent;
+	outline: none;
+	height: 30px;
+	width: 200px;
+}
+
+.bootstrap-select.form-control .btn-default {
+	color: inherit;
+	padding: 4px 6px 4px;
+	border-radius: 4px;
+	border: 1px solid #e5e6e7;
+	outline: none;
+	height: 31px;
+	background: #FFFFFF none
+}
+
+.file-input .btn-default {
+	color: inherit;
+	background: white;
+	border: 1px solid #e7eaec;
+}
+
+.select-list .btn-default {
+	color: #555;
+	padding: 5px 5px;
+	border: 1px solid #ddd;
+	border-radius: 4px;
+	background: transparent;
+	outline: none;
+	height: 30px;
+	width: 200px;
+}
+
+.select-list .btn-default:hover,.select-list .btn-default:focus,.select-list .btn-default:active,.select-list .btn-default.active,.open .dropdown-toggle.btn-default {
+	color: #555;
+	background-color: #e4e4e4;
+	border-color: #b2b2b2
+}
+
+.select-list .bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
+	height: 30px;
+	width: 200px;
+}
+
+.select-list .bootstrap-select > .dropdown-toggle.bs-placeholder,
+.select-list .bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
+.select-list .bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
+.select-list .bootstrap-select > .dropdown-toggle.bs-placeholder:active {
+	color: inherit;
+	font-size: 13px;
+}
+
+.select-list .bootstrap-select .dropdown-toggle .caret {
+	position: inherit;
+}
+
+.select-list .select-selectpicker li {
+	float: none;
+}
+
+.select-list .dropdown-menu>li>a,.bootstrap-select.form-control .dropdown-menu>li>a {
+	line-height: inherit;
+}
+
+.select-list .dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a,.bootstrap-select.form-control .dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{
+	color: #fff;
+	text-decoration: none;
+	background-color: #12889a
+}
+
+.select-list .select2-container--bootstrap {
+	width: 200px!important;
+	display: inline-block;
+}
+
+.select-list .select2-container--bootstrap .select2-selection {
+	border-radius: 6px;
+}
+
+.select-list .select2-container--bootstrap .select2-selection--single {
+	height: 30px!important;
+	padding: 5px 10px;
+}
+
+.select-list .select-time input {
+	width: 93px;
+}
+
+.select-time label,.select-time span,.select-time input {
+	float: left;
+}
+
+@media (max-width:767px) {
+	.select-time label,.select-time span,.select-time input {
+		float: none;
+	}
+	.select-list .select-time input {
+		width: 200px;
+	}
+}
+
+.select-time label {
+	margin-top: 5px;
+}
+
+.select-time span {
+	display: block;
+	margin: 5px 5px;
+}
+
+.search-btn {
+	background-color: #1ab394;
+	border-color: #1ab394;
+	color: #FFF;
+	margin-bottom: 5px;
+	display: inline-block;
+	padding: 6px 12px;
+	margin-bottom: 0;
+	font-size: 14px;
+	font-weight: 400;
+	line-height: 1.42857143;
+	text-align: center;
+	white-space: nowrap;
+	border-radius: 3px;
+	vertical-align: middle;
+	cursor: pointer;
+}
+
+.select-title{
+	color:#3d5266;
+	font-size:15px;
+	padding:10px 0px;
+	font-weight: normal;
+}
+
+/** 表格查询数据 **/
+.table-striped {
+	min-height: 75%;
+}
+
+.table-striped .bootstrap-table {
+	border: 0px!important;
+}
+
+.table-striped table>thead>tr>th, .table-striped table>tbody>tr>th, .table-striped table>tfoot>tr>th, .table-striped table>thead>tr>td, .table-striped table>tbody>tr>td, .table-striped table>tfoot>tr>td {
+	border-bottom: 1px solid #e7eaec!important;
+	background-color: transparent;
+	border: 0px;
+}
+
+
+.table-bordered table>thead>tr>th:first-child, .table-bordered table>tbody>tr>td:first-child {
+	border-left: 1px solid #ddd;
+}
+
+.table-bordered table>thead>tr>th:last-child, .table-bordered table>tbody>tr>td:last-child {
+	border-right: 1px solid #ddd;
+}
+
+.table-bordered table>thead>tr>th, .table-bordered table>tbody>tr>td {
+	border-top: 1px solid #ddd!important;
+	border-bottom: 1px solid #ddd;
+}
+
+.fixed-table-footer {
+	border-top: 0px solid #ddd;
+}
+
+.fixed-table-container {
+	border: 0px solid #ddd;
+}
+
+.table-striped .table>thead>tr>th, .table-striped .table>tbody>tr>th {
+	border-bottom: 1px solid #ccc!important;
+	border-top: 0px!important;
+}
+
+.table-striped .table>thead:first-child>tr:first-child>th {
+	font-weight: normal;
+	font-size: 13px
+}
+.table-striped table thead {
+	background-color: #eff3f8;
+}
+
+.fixed-table-container thead th  >.both{
+	display: inline-block
+}
+
+.editable-input .input-sm {
+	height: 32px!important;
+}
+
+/** 表格冻结列样式 **/
+.left-fixed-table-columns, .left-fixed-body-columns {
+	position: absolute;
+	background-color: #fff;
+	display: none;
+	box-sizing: border-box;
+	overflow: hidden;
+}
+
+.left-fixed-table-columns .table, .left-fixed-body-columns .table {
+	border-right: 1px solid #ddd;
+}
+
+.left-fixed-table-columns .table.table-no-bordered, .left-fixed-body-columns .table.table-no-bordered {
+	border-right: 1px solid transparent;
+}
+
+.left-fixed-body-columns table {
+	position: absolute;
+	animation: none;
+}
+
+.bootstrap-table .table-hover > tbody > tr.hover > td {
+	background-color: #f5f5f5;
+}
+
+.right-fixed-table-columns{
+	position: absolute;
+	right:63px;
+	border-left:1px solid #ddd;
+	display: none;
+	z-index:100;
+}
+
+/** 表格全屏样式 **/
+.bootstrap-table.fullscreen {
+	position: fixed;
+	top: 0;
+	left: 0;
+	z-index: 1050;
+	width: 100%!important;
+	background: #FFF;
+}
+
+/** 表格树样式 **/
+.bootstrap-tree-table .treetable-indent {width:16px; height: 16px; display: inline-block; position: relative;}
+.bootstrap-tree-table .treetable-expander {width:16px; height: 16px; display: inline-block; position: relative; cursor: pointer;}
+.bootstrap-tree-table .treetable-selected{background: #f5f5f5 !important;}
+.bootstrap-tree-table .treetable-table{border:0 !important;margin-bottom:0}
+.bootstrap-tree-table .treetable-table tbody {display:block;height:auto;overflow-y:auto;}
+.bootstrap-tree-table .treetable-table thead, .treetable-table tbody tr {display:table;width:100%;table-layout:fixed;}
+.bootstrap-tree-table .treetable-thead th{line-height:24px;border: 0 !important;border-radius: 4px;border-left:0px solid #e7eaec !important;border-bottom:1px solid #ccc!important;text-align: left;}
+.bootstrap-tree-table .treetable-thead tr :first-child{border-left:0 !important}
+.bootstrap-tree-table .treetable-tbody td{border: 0 !important;border-left:0px solid #e7eaec !important;border-bottom:1px solid #e7eaec!important;white-space: nowrap; text-overflow: ellipsis;}
+.bootstrap-tree-table .treetable-tbody tr :first-child{border-left:0 !important}
+.bootstrap-tree-table .treetable-bars .tool-left, .bootstrap-tree-table .treetable-bars .tool-right{margin-top: 10px; margin-bottom: 10px;}
+.bootstrap-tree-table .treetable-bars .tool-left{float: left;}
+.bootstrap-tree-table .treetable-bars .tool-right{float: right;}
+.bootstrap-tree-table .treetable-bars .columns li label{display: block;padding: 3px 20px;clear: both;font-weight: 400;line-height: 1.428571429;max-width: 100%;margin-bottom: 5px;cursor:pointer;}
+.bootstrap-tree-table .table{border-bottom: 0px solid #e7eaec!important;}
+
+/** 首页样式 **/
+.ax_close_max {
+	position: fixed;
+	top: 5px;
+	left: 5px;
+	z-index: 9999;
+	display: none;
+	color: #ccc;
+}
+.navbar-right > .user-menu > .dropdown-menu {
+	border-top-right-radius:0;
+	border-top-left-radius:0;
+	padding:1px 0 0 0;
+	border-top-width:0;
+	width:138px;
+}
+.navbar-right > .user-menu .user-image {
+	float:left;
+	width:27px;
+	height:27px;
+	border-radius:50%;
+	margin-right:8px;
+	margin-top:-3px;
+}
+@media (max-width:767px) {
+	.navbar-right > .user-menu .user-image {
+		float:none;
+		margin-right:0;
+		margin-top:-8px;
+		line-height:10px;
+	}
+}.dropdown-menu > li > a > .glyphicon,.dropdown-menu > li > a > .fa,.dropdown-menu > li > a > .ion {
+	 margin-right:10px;
+ }
+.dropdown-menu > li > a:hover {
+	background-color:#e1e3e9;
+	color:#333;
+}
+.dropdown-menu > .divider {
+	background-color:#eee;
+}
+
+/** 表单布局 **/
+.form-header {
+	font-size:15px;
+	color:#6379bb;
+	border-bottom:1px solid #ddd;
+	margin:8px 10px 25px 10px;
+	padding-bottom:5px
+}
+
+.main-content {
+	background-color: #ffffff;
+	color: inherit;
+	padding: 10px 15px 15px 15px;
+	border-color: #e7eaec;
+	-webkit-border-image: none;
+	-o-border-image: none;
+	border-image: none;
+	border-width: 1px 0px;
+}
+
+/** 表格跳转样式 **/
+.pageGo input {
+	height: 32px;
+	width: 50px;
+	margin-left: 5px;
+	margin-right: 5px;
+	text-align: center;
+	display: block;
+	float:left;
+}
+.pageGo button {
+	height: 32px;
+	display: block;
+	float:left;
+}
+
+/** 表格拖拽样式 **/
+.reorder_rows_onDragClass td {
+	color:yellow!important;
+	background-color:#999!important;
+	text-shadow:0 0 10px black,0 0 10px black,0 0 8px black,0 0 6px black,0 0 6px black;
+	box-shadow:0 12px 14px -12px #111 inset,0 -2px 2px -1px #333 inset
+}
+
+/** 表格选中样式 **/
+.fixed-table-container .selected {
+	background-color: #E8F7FD;
+	color: #1890ff;
+}
+
+/** 滚动条样式 **/
+::-webkit-scrollbar-track {
+	background-color: #F5F5F5;
+}
+
+/** 气泡弹出框样式 **/
+.popover {
+	font-size: 13px;
+	max-width: unset;
+}
+
+.popover-title {
+	padding: 8px 14px;
+	margin: 0 !important;
+	font-size: 14px;
+	background-color: #f7f7f7;
+	border-bottom: 1px solid #ebebeb;
+	border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+	padding: 5px;
+}
+
+/** 向上滚动样式 **/
+#scroll-up {
+	border-width: 0;
+	position: fixed;
+	right: 2px;
+	z-index: 99;
+	-webkit-transition-duration: .3s;
+	transition-duration: .3s;
+	opacity: 0;
+	filter: alpha(opacity=0);
+	bottom: -24px;
+	visibility: hidden;
+	background-color: #aaa;
+	color: #fff;
+	font-size: 14px;
+	display: none;
+}
+
+#scroll-up.display {
+	opacity: .7;
+	filter: alpha(opacity=70);
+	bottom: 2px;
+	visibility: visible;
+}
+
+/* 设置滚动条样式 */
+::-webkit-scrollbar {
+	width: 6px;
+	height: 10px;
+	background-color: #F5F5F5;
+}
+
+::-webkit-scrollbar-thumb {
+	border-radius: 6px;
+	background-color: #999;
+}
+
+/* 设置placeholder样式 */
+::-webkit-input-placeholder {
+	color: #b3b3b3!important;
+}
+
+:-moz-placeholder {
+	color: #b3b3b3!important;
+}
+
+::-moz-placeholder {
+	color: #b3b3b3!important;
+}
+
+:-ms-input-placeholder {
+	color: #b3b3b3!important;
+}
\ No newline at end of file
diff --git a/src/main/resources/static/huaheng/css/huahengUI.min.css b/src/main/resources/static/huaheng/css/huahengUI.min.css
deleted file mode 100644
index c9ebe96..0000000
--- a/src/main/resources/static/huaheng/css/huahengUI.min.css
+++ /dev/null
@@ -1,5 +0,0 @@
-/*!
- *   huaheng.css
- *   Author: huaheng
- */
-.box{position:relative;border-radius:3px;background:#fff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3}.open .btn-box-tool,.btn-box-tool:hover{color:#606c84}.box-main{margin:0;border:0;padding-top:2px;border-radius:0;box-shadow:none}.box-main>.box-header{border-bottom:1px solid #eee;padding:12px 10px 2px 15px}.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1}.box-main>.box-header .box-title{font-size:16px;margin-bottom:13px;float:left}.box-main>.box-header .box-title .fa{font-size:14px;padding-right:3px;margin-top:-2px}.box-main>.box-header .box-tools{position:relative;top:-5px;right:0}.box-main>.box-header .box-tools .btn{padding:3px 10px 5px 10px;font-size:14px;margin-bottom:2px}.box-main>.box-header .box-tools .btn-box-tool{padding:4px 2px}.box-main form>.box-footer,.nav-main form>.box-footer{background:#fafafa}.box-main form>.box-footer .row,.nav-main form>.box-footer .row{margin:5px 0 5px -25px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#1ab394;border-color:#1ab394;padding:1px 10px;color:#fff}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff}.select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px}label.error{position:absolute;right:18px;top:5px;color:#ef392b;font-size:12px}.Validform_error,input.error,select.error{background-color:#fbe2e2;border-color:#c66161;color:#c00}.Validform_wrong,.Validform_right,.Validform_warning{display:inline-block;height:20px;font-size:12px;vertical-align:middle;padding-left:25px}.loaderbox{display:inline-block;min-width:125px;padding:10px;margin:0 auto;color:#000!important;font-size:13px;font-weight:400;text-align:center;vertical-align:middle;border:1px solid #ddd;background-color:#eee;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 1px 8px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 8px rgba(0,0,0,0.1);box-shadow:0 1px 8px rgba(0,0,0,0.1)}.loaderbox .loading-activity{float:left;width:18px;height:18px;border:solid 2px transparent;border-top-color:#000;border-left-color:#000;border-radius:10px;-webkit-animation:pace-spinner 400ms linear infinite;-moz-animation:pace-spinner 400ms linear infinite;-ms-animation:pace-spinner 400ms linear infinite;-o-animation:pace-spinner 400ms linear infinite;animation:pace-spinner 400ms linear infinite}@media(max-width:767px){.loading-activity{width:18px;height:18px}}@-ms-keyframes pace-spinner{0%{-ms-transform:rotate(0deg);transform:rotate(0deg)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes pace-spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}ul{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}li{list-style:none}.time-input{display:block;width:100%;padding-left:10px}label{font-weight:normal}.container-div{padding:10px 35px;height:100%}.container-div .row{height:100%}.select-info{width:100%;background:#fff;border-radius:6px;margin-top:10px;padding-top:13px;padding-bottom:13px;box-shadow:1px 1px 3px rgba(0,0,0,.2)}.select-info{position:relative}.select-info .save-btn,.select-info .close-btn{width:20px;position:absolute;right:20px;top:15px;cursor:pointer}.select-info .save-btn{right:50px}.select-info .col-sm-6 .control-label{color:#333}.select-list li{float:left;color:#333;margin:5px 15px 5px 0}.select-list li input{border:1px solid #ddd;border-radius:4px;background:transparent;outline:0;height:30px;width:133px}.select-list li .submit-btn{border:0;border-radius:4px;background:transparent;outline:0;width:40px;height:23px}.select-list li select{border:1px solid #ddd;border-radius:4px;background:transparent;outline:0;height:30px;width:133px}.select-list .time input{width:133px}.time label,.time span,.time input{float:left}.time label{margin-top:5px}.time span{display:block;margin:5px 5px}.search-btn{background-color:#1ab394;border-color:#1ab394;color:#FFF;margin-bottom:5px;display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;border-radius:3px;vertical-align:middle;cursor:pointer}.table-striped{min-height:75%}.table-striped .bootstrap-table{border:0!important}.table-striped .table,.fixed-table-container,.table-striped .table,.table-striped .table,.table-striped .table>thead>tr>th,.table-striped .table>tbody>tr>th,.table-striped .table>tfoot>tr>th,.table-striped .table>thead>tr>td,.table-striped .table>tbody>tr>td,.table-striped .table>tfoot>tr>td{background-color:transparent!important;border:0}.table-striped .table>thead>tr>th,.table-striped .table>tbody>tr>th{border-bottom:1px solid #ccc!important;border-top:0!important}.table-striped .table>thead:first-child>tr:first-child>th{color:#333}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}
\ No newline at end of file
diff --git a/src/main/resources/static/huaheng/js/huahengUI.js b/src/main/resources/static/huaheng/js/huahengUI.js
index 6380f25..bdf8d0f 100644
--- a/src/main/resources/static/huaheng/js/huahengUI.js
+++ b/src/main/resources/static/huaheng/js/huahengUI.js
@@ -795,6 +795,35 @@ var table = {
             		content: url
             	});
             },
+            // 弹出层指定参数选项
+            openOptions: function (options) {
+                var _url = $.common.isEmpty(options.url) ? "/404.html" : options.url;
+                var _title = $.common.isEmpty(options.title) ? "系统窗口" : options.title;
+                var _width = $.common.isEmpty(options.width) ? "800" : options.width;
+                var _height = $.common.isEmpty(options.height) ? ($(window).height() - 50) : options.height;
+                var _btn = ['<i class="fa fa-check"></i> 确认', '<i class="fa fa-close"></i> 关闭'];
+                if ($.common.isEmpty(options.yes)) {
+                    options.yes = function(index, layero) {
+                        options.callBack(index, layero);
+                    }
+                }
+                layer.open({
+                    type: 2,
+                    maxmin: true,
+                    shade: 0.3,
+                    title: _title,
+                    fix: false,
+                    area: [_width + 'px', _height + 'px'],
+                    content: _url,
+                    shadeClose: $.common.isEmpty(options.shadeClose) ? true : options.shadeClose,
+                    skin: options.skin,
+                    btn: $.common.isEmpty(options.btn) ? _btn : options.btn,
+                    yes: options.yes,
+                    cancel: function () {
+                        return true;
+                    }
+                });
+            },
             // 弹出层全屏
             openFull: function (title, url, width, height) {
             	//如果是移动端,就使用自适应大小弹窗
@@ -979,6 +1008,26 @@ var table = {
             	var url = table.options.updateUrl.replace("{id}", id);
             	$.modal.open("修改" + table.options.modalName, url);
             },
+            // 修改信息,以tab页展现
+            editTab: function(id) {
+                table.set();
+                $.modal.openTab("修改" + table.options.modalName, $.operate.editUrl(id));
+            },
+            // 修改访问地址
+            editUrl: function(id) {
+                var url = "/404.html";
+                if ($.common.isNotEmpty(id)) {
+                    url = table.options.updateUrl.replace("{id}", id);
+                } else {
+                    var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
+                    if (id.length == 0) {
+                        $.modal.alertWarning("请至少选择一条记录");
+                        return;
+                    }
+                    url = table.options.updateUrl.replace("{id}", id);
+                }
+                return url;
+            },
             //查看
             checklook: function(id) {
                 table.set();
@@ -1031,6 +1080,187 @@ var table = {
             	$.modal.closeLoading();
             }
         },
+        // 树插件封装处理
+        tree: {
+            _option: {},
+            _lastValue: {},
+            // 初始化树结构
+            init: function(options) {
+                var defaults = {
+                    id: "tree",                    // 属性ID
+                    expandLevel: 0,                // 展开等级节点
+                    view: {
+                        selectedMulti: false,      // 设置是否允许同时选中多个节点
+                        nameIsHTML: true           // 设置 name 属性是否支持 HTML 脚本
+                    },
+                    check: {
+                        enable: false,             // 置 zTree 的节点上是否显示 checkbox / radio
+                        nocheckInherit: true,      // 设置子节点是否自动继承
+                    },
+                    data: {
+                        key: {
+                            title: "title"         // 节点数据保存节点提示信息的属性名称
+                        },
+                        simpleData: {
+                            enable: true           // true / false 分别表示 使用 / 不使用 简单数据模式
+                        }
+                    },
+                };
+                var options = $.extend(defaults, options);
+                $.tree._option = options;
+                // 树结构初始化加载
+                var setting = {
+                    callback: {
+                        onClick: options.onClick,                      // 用于捕获节点被点击的事件回调函数
+                        onCheck: options.onCheck,                      // 用于捕获 checkbox / radio 被勾选 或 取消勾选的事件回调函数
+                        onDblClick: options.onDblClick                 // 用于捕获鼠标双击之后的事件回调函数
+                    },
+                    check: options.check,
+                    view: options.view,
+                    data: options.data
+                };
+                $.get(options.url, function(data) {
+                    var treeId = $("#treeId").val();
+                    tree = $.fn.zTree.init($("#" + options.id), setting, data);
+                    $._tree = tree;
+                    for (var i = 0; i < options.expandLevel; i++) {
+                        var nodes = tree.getNodesByParam("level", i);
+                        for (var j = 0; j < nodes.length; j++) {
+                            tree.expandNode(nodes[j], true, false, false);
+                        }
+                    }
+                    var node = tree.getNodesByParam("id", treeId, null)[0];
+                    $.tree.selectByIdName(treeId, node);
+                });
+            },
+            // 搜索节点
+            searchNode: function() {
+                // 取得输入的关键字的值
+                var value = $.common.trim($("#keyword").val());
+                if ($.tree._lastValue == value) {
+                    return;
+                }
+                // 保存最后一次搜索名称
+                $.tree._lastValue = value;
+                var nodes = $._tree.getNodes();
+                // 如果要查空字串,就退出不查了。
+                if (value == "") {
+                    $.tree.showAllNode(nodes);
+                    return;
+                }
+                $.tree.hideAllNode(nodes);
+                // 根据搜索值模糊匹配
+                $.tree.updateNodes($._tree.getNodesByParamFuzzy("name", value));
+            },
+            // 根据Id和Name选中指定节点
+            selectByIdName: function(treeId, node) {
+                if ($.common.isNotEmpty(treeId) && treeId == node.id) {
+                    $._tree.selectNode(node, true);
+                }
+            },
+            // 显示所有节点
+            showAllNode: function(nodes) {
+                nodes = $._tree.transformToArray(nodes);
+                for (var i = nodes.length - 1; i >= 0; i--) {
+                    if (nodes[i].getParentNode() != null) {
+                        $._tree.expandNode(nodes[i], true, false, false, false);
+                    } else {
+                        $._tree.expandNode(nodes[i], true, true, false, false);
+                    }
+                    $._tree.showNode(nodes[i]);
+                    $.tree.showAllNode(nodes[i].children);
+                }
+            },
+            // 隐藏所有节点
+            hideAllNode: function(nodes) {
+                var tree = $.fn.zTree.getZTreeObj("tree");
+                var nodes = $._tree.transformToArray(nodes);
+                for (var i = nodes.length - 1; i >= 0; i--) {
+                    $._tree.hideNode(nodes[i]);
+                }
+            },
+            // 显示所有父节点
+            showParent: function(treeNode) {
+                var parentNode;
+                while ((parentNode = treeNode.getParentNode()) != null) {
+                    $._tree.showNode(parentNode);
+                    $._tree.expandNode(parentNode, true, false, false);
+                    treeNode = parentNode;
+                }
+            },
+            // 显示所有孩子节点
+            showChildren: function(treeNode) {
+                if (treeNode.isParent) {
+                    for (var idx in treeNode.children) {
+                        var node = treeNode.children[idx];
+                        $._tree.showNode(node);
+                        $.tree.showChildren(node);
+                    }
+                }
+            },
+            // 更新节点状态
+            updateNodes: function(nodeList) {
+                $._tree.showNodes(nodeList);
+                for (var i = 0, l = nodeList.length; i < l; i++) {
+                    var treeNode = nodeList[i];
+                    $.tree.showChildren(treeNode);
+                    $.tree.showParent(treeNode)
+                }
+            },
+            // 获取当前被勾选集合
+            getCheckedNodes: function(column) {
+                var _column = $.common.isEmpty(column) ? "id" : column;
+                var nodes = $._tree.getCheckedNodes(true);
+                return $.map(nodes, function (row) {
+                    return row[_column];
+                }).join();
+            },
+            // 不允许根父节点选择
+            notAllowParents: function(_tree) {
+                var nodes = _tree.getSelectedNodes();
+                if(nodes.length == 0){
+                    $.modal.msgError("请选择节点后提交");
+                    return false;
+                }
+                for (var i = 0; i < nodes.length; i++) {
+                    if (nodes[i].level == 0) {
+                        $.modal.msgError("不能选择根节点(" + nodes[i].name + ")");
+                        return false;
+                    }
+                    if (nodes[i].isParent) {
+                        $.modal.msgError("不能选择父节点(" + nodes[i].name + ")");
+                        return false;
+                    }
+                }
+                return true;
+            },
+            // 不允许最后层级节点选择
+            notAllowLastLevel: function(_tree) {
+                var nodes = _tree.getSelectedNodes();
+                for (var i = 0; i < nodes.length; i++) {
+                    if (!nodes[i].isParent) {
+                        $.modal.msgError("不能选择最后层级节点(" + nodes[i].name + ")");
+                        return false;
+                    }
+                }
+                return true;
+            },
+            // 隐藏/显示搜索栏
+            toggleSearch: function() {
+                $('#search').slideToggle(200);
+                $('#btnShow').toggle();
+                $('#btnHide').toggle();
+                $('#keyword').focus();
+            },
+            // 折叠
+            collapse: function() {
+                $._tree.expandAll(false);
+            },
+            // 展开
+            expand: function() {
+                $._tree.expandAll(true);
+            }
+        },
         // 通用方法封装处理
     	common: {
             // 判断字符串是否是以start开头
diff --git a/src/main/resources/static/img/blue.png b/src/main/resources/static/img/blue.png
new file mode 100644
index 0000000..7081c95
--- /dev/null
+++ b/src/main/resources/static/img/blue.png
diff --git a/src/main/resources/static/js/jquery.tmpl.js b/src/main/resources/static/js/jquery.tmpl.js
new file mode 100644
index 0000000..7d130d2
--- /dev/null
+++ b/src/main/resources/static/js/jquery.tmpl.js
@@ -0,0 +1,492 @@
+/*!
+ * jQuery Templates Plugin 1.0.0pre
+ * http://github.com/jquery/jquery-tmpl
+ * Requires jQuery 1.4.2
+ *
+ * Copyright 2011, Software Freedom Conservancy, Inc.
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+(function( factory ) {
+	if (typeof define === 'function' && define.amd) {
+		// Loading from AMD script loader. Register as an anonymous module.
+		define( ['jquery'], factory );
+	} else {
+		// Browser using plain <script> tag
+		factory( jQuery );
+	}
+}(function( jQuery ){
+	var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
+		newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = [];
+
+	function newTmplItem( options, parentItem, fn, data ) {
+		// Returns a template item data structure for a new rendered instance of a template (a 'template item').
+		// The content field is a hierarchical array of strings and nested items (to be
+		// removed and replaced by nodes field of dom elements, once inserted in DOM).
+		var newItem = {
+			data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}),
+			_wrap: parentItem ? parentItem._wrap : null,
+			tmpl: null,
+			parent: parentItem || null,
+			nodes: [],
+			calls: tiCalls,
+			nest: tiNest,
+			wrap: tiWrap,
+			html: tiHtml,
+			update: tiUpdate
+		};
+		if ( options ) {
+			jQuery.extend( newItem, options, { nodes: [], parent: parentItem });
+		}
+		if ( fn ) {
+			// Build the hierarchical content to be used during insertion into DOM
+			newItem.tmpl = fn;
+			newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem );
+			newItem.key = ++itemKey;
+			// Keep track of new template item, until it is stored as jQuery Data on DOM element
+			(stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;
+		}
+		return newItem;
+	}
+
+	// Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core).
+	jQuery.each({
+		appendTo: "append",
+		prependTo: "prepend",
+		insertBefore: "before",
+		insertAfter: "after",
+		replaceAll: "replaceWith"
+	}, function( name, original ) {
+		jQuery.fn[ name ] = function( selector ) {
+			var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems,
+				parent = this.length === 1 && this[0].parentNode;
+
+			appendToTmplItems = newTmplItems || {};
+			if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+				insert[ original ]( this[0] );
+				ret = this;
+			} else {
+				for ( i = 0, l = insert.length; i < l; i++ ) {
+					cloneIndex = i;
+					elems = (i > 0 ? this.clone(true) : this).get();
+					jQuery( insert[i] )[ original ]( elems );
+					ret = ret.concat( elems );
+				}
+				cloneIndex = 0;
+				ret = this.pushStack( ret, name, insert.selector );
+			}
+			tmplItems = appendToTmplItems;
+			appendToTmplItems = null;
+			jQuery.tmpl.complete( tmplItems );
+			return ret;
+		};
+	});
+
+	jQuery.fn.extend({
+		// Use first wrapped element as template markup.
+		// Return wrapped set of template items, obtained by rendering template against data.
+		tmpl: function( data, options, parentItem ) {
+			return jQuery.tmpl( this[0], data, options, parentItem );
+		},
+
+		// Find which rendered template item the first wrapped DOM element belongs to
+		tmplItem: function() {
+			return jQuery.tmplItem( this[0] );
+		},
+
+		// Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
+		template: function( name ) {
+			return jQuery.template( name, this[0] );
+		},
+
+		domManip: function( args, table, callback, options ) {
+			if ( args[0] && jQuery.isArray( args[0] )) {
+				var dmArgs = jQuery.makeArray( arguments ), elems = args[0], elemsLength = elems.length, i = 0, tmplItem;
+				while ( i < elemsLength && !(tmplItem = jQuery.data( elems[i++], "tmplItem" ))) {}
+				if ( tmplItem && cloneIndex ) {
+					dmArgs[2] = function( fragClone ) {
+						// Handler called by oldManip when rendered template has been inserted into DOM.
+						jQuery.tmpl.afterManip( this, fragClone, callback );
+					};
+				}
+				oldManip.apply( this, dmArgs );
+			} else {
+				oldManip.apply( this, arguments );
+			}
+			cloneIndex = 0;
+			if ( !appendToTmplItems ) {
+				jQuery.tmpl.complete( newTmplItems );
+			}
+			return this;
+		}
+	});
+
+	jQuery.extend({
+		// Return wrapped set of template items, obtained by rendering template against data.
+		tmpl: function( tmpl, data, options, parentItem ) {
+			var ret, topLevel = !parentItem;
+			if ( topLevel ) {
+				// This is a top-level tmpl call (not from a nested template using {{tmpl}})
+				parentItem = topTmplItem;
+				tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl );
+				wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
+			} else if ( !tmpl ) {
+				// The template item is already associated with DOM - this is a refresh.
+				// Re-evaluate rendered template for the parentItem
+				tmpl = parentItem.tmpl;
+				newTmplItems[parentItem.key] = parentItem;
+				parentItem.nodes = [];
+				if ( parentItem.wrapped ) {
+					updateWrapped( parentItem, parentItem.wrapped );
+				}
+				// Rebuild, without creating a new template item
+				return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) ));
+			}
+			if ( !tmpl ) {
+				return []; // Could throw...
+			}
+			if ( typeof data === "function" ) {
+				data = data.call( parentItem || {} );
+			}
+			if ( options && options.wrapped ) {
+				updateWrapped( options, options.wrapped );
+			}
+			ret = jQuery.isArray( data ) ?
+				jQuery.map( data, function( dataItem ) {
+					return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null;
+				}) :
+				[ newTmplItem( options, parentItem, tmpl, data ) ];
+			return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret;
+		},
+
+		// Return rendered template item for an element.
+		tmplItem: function( elem ) {
+			var tmplItem;
+			if ( elem instanceof jQuery ) {
+				elem = elem[0];
+			}
+			while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {}
+			return tmplItem || topTmplItem;
+		},
+
+		// Set:
+		// Use $.template( name, tmpl ) to cache a named template,
+		// where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
+		// Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
+
+		// Get:
+		// Use $.template( name ) to access a cached template.
+		// Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
+		// will return the compiled template, without adding a name reference.
+		// If templateString includes at least one HTML tag, $.template( templateString ) is equivalent
+		// to $.template( null, templateString )
+		template: function( name, tmpl ) {
+			if (tmpl) {
+				// Compile template and associate with name
+				if ( typeof tmpl === "string" ) {
+					// This is an HTML string being passed directly in.
+					tmpl = buildTmplFn( tmpl );
+				} else if ( tmpl instanceof jQuery ) {
+					tmpl = tmpl[0] || {};
+				}
+				if ( tmpl.nodeType ) {
+					// If this is a template block, use cached copy, or generate tmpl function and cache.
+					tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML ));
+					// Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space.
+					// This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x.
+					// To correct this, include space in tag: foo="${ x }" -> foo="value of x"
+				}
+				return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;
+			}
+			// Return named compiled template
+			return name ? (typeof name !== "string" ? jQuery.template( null, name ):
+				(jQuery.template[name] ||
+					// If not in map, and not containing at least on HTML tag, treat as a selector.
+					// (If integrated with core, use quickExpr.exec)
+					jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null;
+		},
+
+		encode: function( text ) {
+			// Do HTML encoding replacing < > & and ' and " by corresponding entities.
+			return ("" + text).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;");
+		}
+	});
+
+	jQuery.extend( jQuery.tmpl, {
+		tag: {
+			"tmpl": {
+				_default: { $2: "null" },
+				open: "if($notnull_1){__=__.concat($item.nest($1,$2));}"
+				// tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)
+				// This means that {{tmpl foo}} treats foo as a template (which IS a function).
+				// Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.
+			},
+			"wrap": {
+				_default: { $2: "null" },
+				open: "$item.calls(__,$1,$2);__=[];",
+				close: "call=$item.calls();__=call._.concat($item.wrap(call,__));"
+			},
+			"each": {
+				_default: { $2: "$index, $value" },
+				open: "if($notnull_1){$.each($1a,function($2){with(this){",
+				close: "}});}"
+			},
+			"if": {
+				open: "if(($notnull_1) && $1a){",
+				close: "}"
+			},
+			"else": {
+				_default: { $1: "true" },
+				open: "}else if(($notnull_1) && $1a){"
+			},
+			"html": {
+				// Unecoded expression evaluation.
+				open: "if($notnull_1){__.push($1a);}"
+			},
+			"=": {
+				// Encoded expression evaluation. Abbreviated form is ${}.
+				_default: { $1: "$data" },
+				open: "if($notnull_1){__.push($.encode($1a));}"
+			},
+			"!": {
+				// Comment tag. Skipped by parser
+				open: ""
+			}
+		},
+
+		// This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events
+		complete: function( items ) {
+			newTmplItems = {};
+		},
+
+		// Call this from code which overrides domManip, or equivalent
+		// Manage cloning/storing template items etc.
+		afterManip: function afterManip( elem, fragClone, callback ) {
+			// Provides cloned fragment ready for fixup prior to and after insertion into DOM
+			var content = fragClone.nodeType === 11 ?
+				jQuery.makeArray(fragClone.childNodes) :
+				fragClone.nodeType === 1 ? [fragClone] : [];
+
+			// Return fragment to original caller (e.g. append) for DOM insertion
+			callback.call( elem, fragClone );
+
+			// Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data.
+			storeTmplItems( content );
+			cloneIndex++;
+		}
+	});
+
+	//========================== Private helper functions, used by code above ==========================
+
+	function build( tmplItem, nested, content ) {
+		// Convert hierarchical content into flat string array
+		// and finally return array of fragments ready for DOM insertion
+		var frag, ret = content ? jQuery.map( content, function( item ) {
+			return (typeof item === "string") ?
+				// Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.
+				(tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) :
+				// This is a child template item. Build nested template.
+				build( item, tmplItem, item._ctnt );
+		}) :
+		// If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.
+		tmplItem;
+		if ( nested ) {
+			return ret;
+		}
+
+		// top-level template
+		ret = ret.join("");
+
+		// Support templates which have initial or final text nodes, or consist only of text
+		// Also support HTML entities within the HTML markup.
+		ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) {
+			frag = jQuery( middle ).get();
+
+			storeTmplItems( frag );
+			if ( before ) {
+				frag = unencode( before ).concat(frag);
+			}
+			if ( after ) {
+				frag = frag.concat(unencode( after ));
+			}
+		});
+		return frag ? frag : unencode( ret );
+	}
+
+	function unencode( text ) {
+		// Use createElement, since createTextNode will not render HTML entities correctly
+		var el = document.createElement( "div" );
+		el.innerHTML = text;
+		return jQuery.makeArray(el.childNodes);
+	}
+
+	// Generate a reusable function that will serve to render a template against data
+	function buildTmplFn( markup ) {
+		return new Function("jQuery","$item",
+			// Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10).
+			"var $=jQuery,call,__=[],$data=$item.data;" +
+
+			// Introduce the data as local variables using with(){}
+			"with($data){__.push('" +
+
+			// Convert the template into pure JavaScript
+			jQuery.trim(markup)
+				.replace( /([\\'])/g, "\\$1" )
+				.replace( /[\r\t\n]/g, " " )
+				.replace( /\$\{([^\}]*)\}/g, "{{= $1}}" )
+				.replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
+				function( all, slash, type, fnargs, target, parens, args ) {
+					var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect;
+					if ( !tag ) {
+						throw "Unknown template tag: " + type;
+					}
+					def = tag._default || [];
+					if ( parens && !/\w$/.test(target)) {
+						target += parens;
+						parens = "";
+					}
+					if ( target ) {
+						target = unescape( target );
+						args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : "");
+						// Support for target being things like a.toLowerCase();
+						// In that case don't call with template item as 'this' pointer. Just evaluate...
+						expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target;
+						exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
+					} else {
+						exprAutoFnDetect = expr = def.$1 || "null";
+					}
+					fnargs = unescape( fnargs );
+					return "');" +
+						tag[ slash ? "close" : "open" ]
+							.split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" )
+							.split( "$1a" ).join( exprAutoFnDetect )
+							.split( "$1" ).join( expr )
+							.split( "$2" ).join( fnargs || def.$2 || "" ) +
+						"__.push('";
+				}) +
+			"');}return __;"
+		);
+	}
+	function updateWrapped( options, wrapped ) {
+		// Build the wrapped content.
+		options._wrap = build( options, true,
+			// Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string.
+			jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()]
+		).join("");
+	}
+
+	function unescape( args ) {
+		return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null;
+	}
+	function outerHtml( elem ) {
+		var div = document.createElement("div");
+		div.appendChild( elem.cloneNode(true) );
+		return div.innerHTML;
+	}
+
+	// Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance.
+	function storeTmplItems( content ) {
+		var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
+		for ( i = 0, l = content.length; i < l; i++ ) {
+			if ( (elem = content[i]).nodeType !== 1 ) {
+				continue;
+			}
+			elems = elem.getElementsByTagName("*");
+			for ( m = elems.length - 1; m >= 0; m-- ) {
+				processItemKey( elems[m] );
+			}
+			processItemKey( elem );
+		}
+		function processItemKey( el ) {
+			var pntKey, pntNode = el, pntItem, tmplItem, key;
+			// Ensure that each rendered template inserted into the DOM has its own template item,
+			if ( (key = el.getAttribute( tmplItmAtt ))) {
+				while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { }
+				if ( pntKey !== key ) {
+					// The next ancestor with a _tmplitem expando is on a different key than this one.
+					// So this is a top-level element within this template item
+					// Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
+					pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0;
+					if ( !(tmplItem = newTmplItems[key]) ) {
+						// The item is for wrapped content, and was copied from the temporary parent wrappedItem.
+						tmplItem = wrappedItems[key];
+						tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] );
+						tmplItem.key = ++itemKey;
+						newTmplItems[itemKey] = tmplItem;
+					}
+					if ( cloneIndex ) {
+						cloneTmplItem( key );
+					}
+				}
+				el.removeAttribute( tmplItmAtt );
+			} else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) {
+				// This was a rendered element, cloned during append or appendTo etc.
+				// TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
+				cloneTmplItem( tmplItem.key );
+				newTmplItems[tmplItem.key] = tmplItem;
+				pntNode = jQuery.data( el.parentNode, "tmplItem" );
+				pntNode = pntNode ? pntNode.key : 0;
+			}
+			if ( tmplItem ) {
+				pntItem = tmplItem;
+				// Find the template item of the parent element.
+				// (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
+				while ( pntItem && pntItem.key != pntNode ) {
+					// Add this element as a top-level node for this rendered template item, as well as for any
+					// ancestor items between this item and the item of its parent element
+					pntItem.nodes.push( el );
+					pntItem = pntItem.parent;
+				}
+				// Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...
+				delete tmplItem._ctnt;
+				delete tmplItem._wrap;
+				// Store template item as jQuery data on the element
+				jQuery.data( el, "tmplItem", tmplItem );
+			}
+			function cloneTmplItem( key ) {
+				key = key + keySuffix;
+				tmplItem = newClonedItems[key] =
+					(newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent ));
+			}
+		}
+	}
+
+	//---- Helper functions for template item ----
+
+	function tiCalls( content, tmpl, data, options ) {
+		if ( !content ) {
+			return stack.pop();
+		}
+		stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options });
+	}
+
+	function tiNest( tmpl, data, options ) {
+		// nested template, using {{tmpl}} tag
+		return jQuery.tmpl( jQuery.template( tmpl ), data, options, this );
+	}
+
+	function tiWrap( call, wrapped ) {
+		// nested template, using {{wrap}} tag
+		var options = call.options || {};
+		options.wrapped = wrapped;
+		// Apply the template, which may incorporate wrapped content,
+		return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item );
+	}
+
+	function tiHtml( filter, textOnly ) {
+		var wrapped = this._wrap;
+		return jQuery.map(
+			jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ),
+			function(e) {
+				return textOnly ?
+					e.innerText || e.textContent :
+					e.outerHTML || outerHtml(e);
+			});
+	}
+
+	function tiUpdate() {
+		var coll = this.nodes;
+		jQuery.tmpl( null, null, null, this).insertBefore( coll[0] );
+		jQuery( coll ).remove();
+	}
+}));
diff --git a/src/main/resources/templates/include.html b/src/main/resources/templates/include.html
index 77e7a3e..38fa0a1 100644
--- a/src/main/resources/templates/include.html
+++ b/src/main/resources/templates/include.html
@@ -14,8 +14,7 @@
 	<link th:href="@{/css/animate.css}" rel="stylesheet"/>
 	<link th:href="@{/css/style.css}" rel="stylesheet"/>
 	<link th:href="@{/css/checkbox.css}" rel="stylesheet"/>
-	<link th:href="@{/ajax/libs/select/select2.css}" rel="stylesheet"/>
-	<link th:href="@{/huaheng/css/huahengUI.min.css}" rel="stylesheet"/>
+	<link th:href="@{/huaheng/css/huahengUI.css}" rel="stylesheet"/>
 </head>
 <div th:fragment="footer">
 	<script th:src="@{/js/jquery.min.js}"></script>
@@ -42,6 +41,150 @@
 	<script th:src="@{/ajax/libs/layer/layer.min.js}"></script>
 	<script th:src="@{/ajax/libs/layui/layui.js}"></script>
 	<script th:src="@{/ajax/libs/layui/lay/modules/upload.js}"></script>
+	<script th:src="@{/ajax/libs/iCheck/icheck.min.js}"></script>
 	<script th:src="@{/huaheng/js/huahengUI.js?v=2.3.2}"></script>
 	<script th:inline="javascript"> var ctx = [[@{/}]]; </script>
 </div>
+
+<!-- ztree树插件 -->
+<div th:fragment="ztree-css">
+	<link th:href="@{/ajax/libs/jquery-ztree/3.5/css/metro/zTreeStyle.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="ztree-js">
+	<script th:src="@{/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js}"></script>
+</div>
+<!-- select2下拉框插件 -->
+<div th:fragment="select2-css">
+	<link th:href="@{/ajax/libs/select2/select2.min.css}" rel="stylesheet"/>
+	<link th:href="@{/ajax/libs/select2/select2-bootstrap.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="select2-js">
+	<script th:src="@{/ajax/libs/select2/select2.min.js}"></script>
+</div>
+
+<!-- bootstrap-select下拉框插件 -->
+<div th:fragment="bootstrap-select-css">
+	<link th:href="@{/ajax/libs/bootstrap-select/bootstrap-select.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="bootstrap-select-js">
+	<script th:src="@{/ajax/libs/bootstrap-select/bootstrap-select.js}"></script>
+</div>
+
+<!-- datetimepicker日期和时间插件 -->
+<div th:fragment="datetimepicker-css">
+	<link th:href="@{/ajax/libs/datapicker/bootstrap-datetimepicker.min.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="datetimepicker-js">
+	<script th:src="@{/ajax/libs/datapicker/bootstrap-datetimepicker.min.js}"></script>
+</div>
+
+<!-- ui布局插件 -->
+<div th:fragment="layout-latest-css">
+	<link th:href="@{/ajax/libs/jquery-layout/jquery.layout-latest.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="layout-latest-js">
+	<script th:src="@{/ajax/libs/jquery-layout/jquery.layout-latest.js}"></script>
+</div>
+
+<!-- summernote富文本编辑器插件 -->
+<div th:fragment="summernote-css">
+	<link th:href="@{/ajax/libs/summernote/summernote.css}" rel="stylesheet"/>
+	<link th:href="@{/ajax/libs/summernote/summernote-bs3.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="summernote-js">
+	<script th:src="@{/ajax/libs/summernote/summernote.min.js}"></script>
+	<script th:src="@{/ajax/libs/summernote/summernote-zh-CN.js}"></script>
+</div>
+
+<!-- cropbox图像裁剪插件 -->
+<div th:fragment="cropbox-css">
+	<link th:href="@{/ajax/libs/cropbox/cropbox.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="cropbox-js">
+	<script th:src="@{/ajax/libs/cropbox/cropbox.js}"></script>
+</div>
+
+<!-- jasny功能扩展插件 -->
+<div th:fragment="jasny-bootstrap-css">
+	<link th:href="@{/ajax/libs/jasny/jasny-bootstrap.min.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="jasny-bootstrap-js">
+	<script th:src="@{/ajax/libs/jasny/jasny-bootstrap.min.js}"></script>
+</div>
+
+<!-- fileinput文件上传插件 -->
+<div th:fragment="bootstrap-fileinput-css">
+	<link th:href="@{/ajax/libs/bootstrap-fileinput/fileinput.min.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="bootstrap-fileinput-js">
+	<script th:src="@{/ajax/libs/bootstrap-fileinput/fileinput.min.js}"></script>
+</div>
+
+<!-- duallistbox双列表框插件 -->
+<div th:fragment="bootstrap-duallistbox-css">
+	<link th:href="@{/ajax/libs/duallistbox/bootstrap-duallistbox.min.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="bootstrap-duallistbox-js">
+	<script th:src="@{/ajax/libs/duallistbox/bootstrap-duallistbox.min.js}"></script>
+</div>
+
+<!-- suggest搜索自动补全 -->
+<div th:fragment="bootstrap-suggest-js">
+	<script th:src="@{/ajax/libs/suggest/bootstrap-suggest.min.js}"></script>
+</div>
+
+<!-- typeahead搜索自动补全 -->
+<div th:fragment="bootstrap-typeahead-js">
+	<script th:src="@{/ajax/libs/typeahead/bootstrap3-typeahead.min.js}"></script>
+</div>
+
+<!-- 多级联动下拉 -->
+<div th:fragment="jquery-cxselect-js">
+	<script th:src="@{/ajax/libs/cxselect/jquery.cxselect.min.js}"></script>
+</div>
+
+<!-- jsonview格式化和语法高亮JSON格式数据查看插件 -->
+<div th:fragment="jsonview-css">
+	<link th:href="@{/ajax/libs/jsonview/jquery.jsonview.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="jsonview-js">
+	<script th:src="@{/ajax/libs/jsonview/jquery.jsonview.js}"></script>
+</div>
+
+<!-- jquery.steps表单向导插件 -->
+<div th:fragment="jquery-steps-css">
+	<link th:href="@{/ajax/libs/staps/jquery.steps.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="jquery-steps-js">
+	<script th:src="@{/ajax/libs/staps/jquery.steps.min.js}"></script>
+</div>
+
+<!-- ECharts百度统计图表插件 -->
+<div th:fragment="echarts-js">
+	<script th:src="@{/ajax/libs/report/echarts/echarts-all.js}"></script>
+</div>
+
+<!-- peity图表组合插件 -->
+<div th:fragment="peity-js">
+	<script th:src="@{/ajax/libs/report/peity/jquery.peity.min.js}"></script>
+</div>
+
+<!-- sparkline线状图插件 -->
+<div th:fragment="sparkline-js">
+	<script th:src="@{/ajax/libs/report/sparkline/jquery.sparkline.min.js}"></script>
+</div>
+
+<!-- 表格拖拽插件 -->
+<div th:fragment="bootstrap-table-reorder-js">
+	<script th:src="@{/ajax/libs/bootstrap-table/extensions/reorder/bootstrap-table-reorder.js}"></script>
+	<script th:src="@{/ajax/libs/bootstrap-table/extensions/reorder/jquery.tablednd.js}"></script>
+</div>
+
+<!-- 表格行内编辑插件 -->
+<div th:fragment="bootstrap-editable-css">
+	<link th:href="@{/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="bootstrap-table-editable-js">
+	<script th:src="@{/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.min.js}"></script>
+	<script th:src="@{/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.js}"></script>
+</div>
diff --git a/src/main/resources/templates/receipt/receiptDetail/add.html b/src/main/resources/templates/receipt/receiptDetail/add.html
index d4c8a91..1ae0602 100644
--- a/src/main/resources/templates/receipt/receiptDetail/add.html
+++ b/src/main/resources/templates/receipt/receiptDetail/add.html
@@ -25,7 +25,7 @@
             </div>
         </div>
         <div class="form-group">
-            <label class="col-sm-3 control-label">总数量:</label>
+            <label class="col-sm-3 control-label">数量:</label>
             <div class="col-sm-8">
                 <input id="totalQty" name="totalQty" class="form-control" type="text">
             </div>
@@ -134,7 +134,7 @@
         <!--</div>-->
         <div class="form-group">
             <div class="form-control-static col-sm-offset-9">
-                <button type="submit" class="btn btn-primary" >提交</button>
+                <button type="submit" class="btn btn-primary"  >提交</button>
                 <button onclick="$.modal.close()" class="btn btn-danger" type="button">关闭</button>
             </div>
         </div>
@@ -160,13 +160,54 @@
             }
         },
         submitHandler: function(form) {
-            var tableValue = $("#form-receiptDetail-add").serialize();
-            tableValue = formValueReplace(tableValue, "inventorySts", $("#inventorySts option:selected").val());
-            tableValue = formValueReplace(tableValue, "qcCheck", $("#qcCheck option:selected").val());
-            $.operate.save(prefix + "/add", tableValue);
+            $.ajax({
+                cache : true,
+                type : "POST",
+                url : prefix + "/add",
+                data : {
+                    "receiptId": $("input[name='receiptId']").val(),
+                    "receiptCode": $("input[name='receiptCode']").val(),
+                    "companyId": $("input[name='companyId']").val(),
+                    "companyCode": $("input[name='companyCode']").val(),
+                    "sourceLine": $("input[name='sourceLine']").val(),
+                    // "materialId": $("input[name='materialId']").val(),
+                    "materialCode": $("input[name='materialCode']").val(),
+                    "batch": $("input[name='batch']").val(),
+                    "lot": $("input[name='lot']").val(),
+                    "project": $("input[name='project']").val(),
+                    "manufactureDate": $("input[name='manufactureDate']").val(),
+                    "expirationDate": $("input[name='expirationDate']").val(),
+                    "inventorySts": $("#inventorySts option:selected").val(),
+                    "totalQty": $("input[name='totalQty']").val(),
+                    "price": $("input[name='price']").val(),
+                    // "userDef1": $("input[name='userDef1']").val(),
+                    // "userDef2": $("input[name='userDef2']").val(),
+                    // "userDef3": $("input[name='userDef3']").val()
+                },
+                async : false,
+                error : function(request) {
+                    $.modal.alertError("请求失败!");
+                },
+                success : function(data) {
+                    // $.operate.saveSuccess(data);
+                    ajaxSuccess(data);
+                }
+            });
         }
     });
 
+    function ajaxSuccess(result) {
+        if (result.code == web_status.SUCCESS) {
+            $.modal.msgSuccess(result.msg);
+            var index = parent.layer.getFrameIndex(window.name);
+            parent.layer.close(index);
+            window.parent.loadDetail();
+        } else {
+            $.modal.alertError(result.msg);
+        }
+        $.modal.closeLoading();
+    }
+
     $(function () {
         layui.use('laydate', function() {
             var laydate = layui.laydate;
diff --git a/src/main/resources/templates/receipt/receiptHeader/add.html b/src/main/resources/templates/receipt/receiptHeader/add.html
index ca8e0c2..db3a7b9 100644
--- a/src/main/resources/templates/receipt/receiptHeader/add.html
+++ b/src/main/resources/templates/receipt/receiptHeader/add.html
@@ -8,7 +8,7 @@
 			<div class="form-group">
 				<label class="col-sm-3 control-label">入库类型:</label>
 				<div class="col-sm-8">
-					<select id="receiptType" name="receiptType" class="form-control" th:with="receiptHeaderType=${@receiptTypeService.getType()}">
+					<select id="receiptType" name="receiptType" class="form-control"  th:with="receiptHeaderType=${@receiptTypeService.getType()}">
 						<option th:each="dict : ${receiptHeaderType}" th:text="${dict['name']}" th:value="${dict['code']}"></option>
 					</select>
 				</div>
diff --git a/src/main/resources/templates/system/dict/type/tree.html b/src/main/resources/templates/system/dict/type/tree.html
new file mode 100644
index 0000000..bb410d4
--- /dev/null
+++ b/src/main/resources/templates/system/dict/type/tree.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
+<head>
+    <th:block th:include="include :: header" />
+    <th:block th:include="include :: ztree-css" />
+</head>
+<style>
+    body{height:auto;font-family: "Microsoft YaHei";}
+    button{font-family: "SimSun","Helvetica Neue",Helvetica,Arial;}
+</style>
+<body class="hold-transition box box-main">
+<input id="columnId" type="hidden" th:value="${columnId}"/>
+<input id="treeId"   type="hidden" th:value="${dict?.dictId}"/>
+<input id="dictType" type="hidden" th:value="${dict?.dictType}"/>
+<div class="wrapper"><div class="treeShowHideButton" onclick="$.tree.toggleSearch();">
+    <label id="btnShow" title="显示搜索" style="display:none;">︾</label>
+    <label id="btnHide" title="隐藏搜索">︽</label>
+</div>
+    <div class="treeSearchInput" id="search">
+        <label for="keyword">关键字:</label><input type="text" class="empty" id="keyword" maxlength="50">
+        <button class="btn" id="btn" onclick="$.tree.searchNode()"> 搜索 </button>
+    </div>
+    <div id="tree" class="ztree treeselect"></div>
+</div>
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: ztree-js" />
+<script th:inline="javascript">
+    $(function() {
+        var url = ctx + "system/dict/treeData";
+        var options = {
+            url: url,
+            onClick : zOnClick
+        };
+        $.tree.init(options);
+    });
+
+    function zOnClick(event, treeId, treeNode) {
+        $("#dictType").val(treeNode.title);
+    }
+</script>
+</body>
+</html>
diff --git a/src/main/resources/templates/tool/gen/edit.html b/src/main/resources/templates/tool/gen/edit.html
new file mode 100644
index 0000000..4470a0d
--- /dev/null
+++ b/src/main/resources/templates/tool/gen/edit.html
@@ -0,0 +1,523 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
+<head>
+    <th:block th:include="include :: header" />
+    <th:block th:include="include :: select2-css" />
+    <style type="text/css">
+        .select-table table{table-layout:fixed;}.table>thead>tr>th{text-align:center;}.select-table .table td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.form-control{padding:3px 6px 4px;height:30px;}.icheckbox-blue{top:0px;left:6px;}.form-control.select2-hidden-accessible{position:static!important;}.select-table table label.error{position: inherit;}select + label.error{z-index:1;right:40px;}
+    </style>
+</head>
+<body class="gray-bg" style="font: 14px Helvetica Neue, Helvetica, PingFang SC, 微软雅黑, Tahoma, Arial, sans-serif !important;">
+<section class="section-content">
+    <div class="row">
+        <div class="col-xs-12">
+            <div class="ibox float-e-margins">
+                <div class="ibox-content" style="border-style:none;">
+                    <div class="nav-tabs-custom">
+                        <ul class="nav nav-tabs">
+                            <li><a href="#tab-basic" data-toggle="tab" aria-expanded="false">基本信息</a></li>
+                            <li class="active"><a href="#tab-field" data-toggle="tab" aria-expanded="true">字段信息</a></li>
+                            <li><a href="#tab-gen" data-toggle="tab" aria-expanded="false">生成信息</a></li>
+                            <li class="pull-right header">
+                                <i class="fa fa-code"></i> 生成配置
+                            </li>
+                        </ul>
+                        <form id="form-gen-edit" class="form-horizontal" th:object="${table}">
+                            <input name="tableId" type="hidden" th:field="*{tableId}" />
+                            <div class="tab-content">
+                                <!-- 基本信息 -->
+                                <div class="tab-pane" id="tab-basic">
+                                    <div class="row mt20">
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required">表名称:</label>
+                                                <div class="col-sm-8">
+                                                    <input name="tableName" class="form-control" type="text" placeholder="请输入表名称" maxlength="200" th:field="*{tableName}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required">表描述:</label>
+                                                <div class="col-sm-8">
+                                                    <input name="tableComment" class="form-control" type="text" placeholder="请输入表描述" maxlength="500" th:field="*{tableComment}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row">
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required">实体类名称:</label>
+                                                <div class="col-sm-8">
+                                                    <input name="className" class="form-control" type="text" placeholder="请输入实体类名称" maxlength="100" th:field="*{className}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required">作者:</label>
+                                                <div class="col-sm-8">
+                                                    <input name="functionAuthor" class="form-control" type="text" placeholder="请输入作者" maxlength="50" th:field="*{functionAuthor}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row">
+                                        <div class="col-sm-12">
+                                            <div class="form-group">
+                                                <label class="col-xs-2 control-label">备注:</label>
+                                                <div class="col-xs-10">
+                                                    <textarea name="remark" maxlength="500" class="form-control" rows="3"></textarea>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <!-- 字段信息 -->
+                                <div class="tab-pane active" id="tab-field">
+                                    <div class="select-table table-striped" style="margin-top: 0px;padding-top: 0px;padding-bottom: 0px;">
+                                        <table id="bootstrap-table" data-use-row-attr-func="true" data-reorderable-rows="true"></table>
+                                    </div>
+                                </div>
+
+                                <!-- 生成信息 -->
+                                <div class="tab-pane" id="tab-gen">
+                                    <div class="row mt20">
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required">生成模板:</label>
+                                                <div class="col-sm-8">
+                                                    <select class='form-control' id="tplCategory" name='tplCategory' style="width: 100%">
+                                                        <option value="crud" th:field="*{tplCategory}">单表(增删改查)</option>
+                                                        <option value="tree" th:field="*{tplCategory}">树表(增删改查)</option>
+                                                        <option value="sub" th:field="*{tplCategory}">主子表(增删改查)</option>
+                                                    </select>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required" title="生成在哪个java包下,例如 com.ruoyi.project.system">生成包路径:<i class="fa fa-question-circle-o"></i></label>
+                                                <div class="col-sm-8">
+                                                    <input name="packageName" class="form-control" type="text" placeholder="请输入生成包路径" maxlength="100" th:field="*{packageName}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row">
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required" title="可理解为子系统名,例如 system">生成模块名:<i class="fa fa-question-circle-o"></i></label>
+                                                <div class="col-sm-8">
+                                                    <input name="moduleName" class="form-control" type="text" placeholder="请输入生成模块名" maxlength="30" th:field="*{moduleName}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required" title="可理解为功能英文名,例如 user">生成业务名:<i class="fa fa-question-circle-o"></i></label>
+                                                <div class="col-sm-8">
+                                                    <input name="businessName" class="form-control" type="text" placeholder="请输入生成业务名" maxlength="50" th:field="*{businessName}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row">
+                                        <div class="col-sm-6">
+                                            <div class="form-group">
+                                                <label class="col-sm-4 control-label is-required" title="用作类描述,例如 用户">生成功能名:<i class="fa fa-question-circle-o"></i></label>
+                                                <div class="col-sm-8">
+                                                    <input name="functionName" class="form-control" type="text" placeholder="请输入生成功能名" maxlength="50" th:field="*{functionName}" required>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="hidden" id="subInfo">
+                                        <h4 class="form-header h4">关联信息</h4>
+                                        <div class="row">
+                                            <div class="col-sm-6">
+                                                <div class="form-group">
+                                                    <label class="col-sm-4 control-label is-required" title="关联子表的表名, 如:sys_user">关联子表的表名:<i class="fa fa-question-circle-o"></i></label>
+                                                    <div class="col-sm-8">
+                                                        <select class='type form-control' id="subTableName" name='subTableName' th:attr='data-value=*{subTableName}' style="width: 100%">
+                                                            <option value="">---请选择---</option>
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="col-sm-6">
+                                                <div class="form-group">
+                                                    <label class="col-sm-4 control-label is-required" title="子表关联的外键名, 如:user_id">子表关联的外键名:<i class="fa fa-question-circle-o"></i></label>
+                                                    <div class="col-sm-8">
+                                                        <select class='router form-control' id="subTableFkName" name='subTableFkName' th:attr='data-value=*{subTableFkName}' style="width: 100%">
+                                                            <option value="">---请选择---</option>
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="hidden" id="otherInfo">
+                                        <h4 class="form-header h4">其他信息</h4>
+                                        <div class="row">
+                                            <div class="col-sm-6">
+                                                <div class="form-group">
+                                                    <label class="col-sm-4 control-label is-required" title="树显示的编码字段名, 如:dept_id">树编码字段:<i class="fa fa-question-circle-o"></i></label>
+                                                    <div class="col-sm-8">
+                                                        <select class='form-control' id="treeCode" name='params[treeCode]' style="width: 100%">
+                                                            <option value="">---请选择---</option>
+                                                            <option th:each="column : ${table.columns}" th:text="${column.columnName + ':' + column.columnComment}" th:value="${column.columnName}" th:field="*{treeCode}"></option>
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="col-sm-6">
+                                                <div class="form-group">
+                                                    <label class="col-sm-4 control-label is-required" title="树显示的父编码字段名, 如:parent_Id">树父编码字段:<i class="fa fa-question-circle-o"></i></label>
+                                                    <div class="col-sm-8">
+                                                        <select class='form-control' id="treeParentCode" name='params[treeParentCode]' style="width: 100%">
+                                                            <option value="">---请选择---</option>
+                                                            <option th:each="column : ${table.columns}" th:text="${column.columnName + ':' + column.columnComment}" th:value="${column.columnName}" th:field="*{treeParentCode}"></option>
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="row">
+                                            <div class="col-sm-6">
+                                                <div class="form-group">
+                                                    <label class="col-sm-4 control-label is-required" title="树节点的显示名称字段名, 如:dept_name">树名称字段:<i class="fa fa-question-circle-o"></i></label>
+                                                    <div class="col-sm-8">
+                                                        <select class='form-control' id="treeName" name='params[treeName]' style="width: 100%">
+                                                            <option value="">---请选择---</option>
+                                                            <option th:each="column : ${table.columns}" th:text="${column.columnName + ':' + column.columnComment}" th:value="${column.columnName}" th:field="*{treeName}"></option>
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+                <div class="box-footer">
+                    <div class="col-sm-offset-5 col-sm-6">
+                        <button type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i>保 存</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</section>
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: select2-js" />
+<th:block th:include="include :: bootstrap-table-reorder-js" />
+<script th:src="@{/js/jquery.tmpl.js}"></script>
+<th:block th:include="include :: jquery-cxselect-js" />
+<script th:inline="javascript">
+    /* 用户信息-修改 */
+    $("#form-table-edit").validate({
+        rules: {
+            tableName: {
+                required: true,
+            },
+        },
+        focusCleanup: true
+    });
+
+    /* 表级联信息 */
+    var data = [[${data}]];
+    $('#subInfo').cxSelect({
+        selects: ['type', 'router'],
+        jsonValue: 'v',
+        data: data
+    });
+
+    function submitHandler() {
+        if ($.validate.form()) {
+            $.operate.saveTab(prefix + "/edit", $("#form-gen-edit").serializeArray());
+        }
+    }
+
+    var prefix = ctx + "tool/gen";
+    $(function() {
+        var options = {
+            url: prefix + "/column/list",
+            sortName: "sort",
+            sortOrder: "desc",
+            height: $(window).height() - 166,
+            pagination: false,
+            showSearch: false,
+            showRefresh: false,
+            showToggle: false,
+            showColumns: false,
+            onLoadSuccess: onLoadSuccess,
+            onReorderRow: onReorderRow,
+            columns: [{
+                title: "序号",
+                width: "5%",
+                formatter: function (value, row, index) {
+                    // 编号隐藏域
+                    var columnIdHtml = $.common.sprintf("<input type='hidden' name='columns[%s].columnId' value='%s'>", index, row.columnId);
+                    // 排序隐藏域
+                    var sortHtml = $.common.sprintf("<input type='hidden' name='columns[%s].sort' value='%s' id='columns_sort_%s'>", index, row.sort, row.columnId);
+                    return columnIdHtml + sortHtml + $.table.serialNumber(index);
+                },
+                cellStyle: function(value, row, index) {
+                    return { css: { "cursor": "move" } };
+                }
+            },
+                {
+                    field: 'columnName',
+                    title: '字段列名',
+                    width: "10%",
+                    class: "nodrag",
+                    cellStyle: function(value, row, index) {
+                        return { css: { "cursor": "default" } };
+                    }
+                },
+                {
+                    field: 'columnComment',
+                    title: '字段描述',
+                    width: "10%",
+                    formatter: function (value, row, index) {
+                        var html = $.common.sprintf("<input class='form-control' type='text' name='columns[%s].columnComment' value='%s'>", index, $.common.nullToStr(value));
+                        return html;
+                    }
+                },
+                {
+                    field: 'columnType',
+                    title: '物理类型',
+                    width: "10%",
+                    class: "nodrag",
+                    cellStyle: function(value, row, index) {
+                        return { css: { "cursor": "default" } };
+                    }
+                },
+                {
+                    field: 'javaType',
+                    title: 'Java类型',
+                    width: "10%",
+                    formatter: function (value, row, index) {
+                        var data = [{ index: index, javaType: value }];
+                        return $("#javaTypeTpl").tmpl(data).html();
+                    }
+                },
+                {
+                    field: 'javaField',
+                    title: 'Java属性',
+                    width: "10%",
+                    formatter: function (value, row, index) {
+                        var html = $.common.sprintf("<input class='form-control' type='text' name='columns[%s].javaField' value='%s' required>", index, value);
+                        return html;
+                    }
+                },
+                {
+                    field: 'isInsert',
+                    title: '插入',
+                    width: "5%",
+                    formatter: function (value, row, index) {
+                        var isCheck = value == 1 ? 'checked' : '';
+                        var html = $.common.sprintf("<label class='check-box'><input type='checkbox' name='columns[%s].isInsert' value='1' %s></label>", index, isCheck);
+                        return html;
+                    }
+                },
+                {
+                    field: 'isEdit',
+                    title: '编辑',
+                    width: "5%",
+                    formatter: function (value, row, index) {
+                        var isCheck = value == 1 ? 'checked' : '';
+                        var html = $.common.sprintf("<label class='check-box'><input type='checkbox' name='columns[%s].isEdit' value='1' %s></label>", index, isCheck);
+                        return html;
+                    }
+                },
+                {
+                    field: 'isList',
+                    title: '列表',
+                    width: "5%",
+                    formatter: function (value, row, index) {
+                        var isCheck = value == 1 ? 'checked' : '';
+                        var html = $.common.sprintf("<label class='check-box'><input type='checkbox' name='columns[%s].isList' value='1' %s></label>", index, isCheck);
+                        return html;
+                    }
+                },
+                {
+                    field: 'isQuery',
+                    title: '查询',
+                    width: "5%",
+                    formatter: function (value, row, index) {
+                        var isCheck = value == 1 ? 'checked' : '';
+                        var html = $.common.sprintf("<label class='check-box'><input type='checkbox' name='columns[%s].isQuery' value='1' %s></label>", index, isCheck);
+                        return html;
+                    }
+                },
+                {
+                    field: 'queryType',
+                    title: '查询方式',
+                    width: "10%",
+                    formatter: function (value, row, index) {
+                        var data = [{ index: index, queryType: value }];
+                        return $("#queryTypeTpl").tmpl(data).html();
+                    }
+                },
+                {
+                    field: 'isRequired',
+                    title: '必填',
+                    width: "5%",
+                    formatter: function (value, row, index) {
+                        var isCheck = value == 1 ? 'checked' : '';
+                        var html = $.common.sprintf("<label class='check-box'><input type='checkbox' name='columns[%s].isRequired' value='1' %s></label>", index, isCheck);
+                        return html;
+                    }
+                },
+                {
+                    field: 'htmlType',
+                    title: '显示类型',
+                    width: "12%",
+                    formatter: function (value, row, index) {
+                        var data = [{ index: index, htmlType: value }];
+                        return $("#htmlTypeTpl").tmpl(data).html();
+                    }
+                },
+                {
+                    field: 'dictType',
+                    title: '字典类型',
+                    width: "13%",
+                    formatter: function (value, row, index) {
+                        var html = $.common.sprintf("<input class='form-control' type='text' name='columns[%s].dictType' value='%s' id='columns_dict_%s'>", index, value, row.columnId);
+                        return "<div class='input-group'>" + html + "<span class='input-group-addon input-sm' onclick='selectDictTree(" + row.columnId + ", this)'><i class='fa fa-search'></i></span></div>";
+                    },
+                    cellStyle: function(value, row, index) {
+                        return { css: { "cursor": "default" } };
+                    }
+                }]
+        };
+        $.table.init(options);
+    });
+
+    // 当所有数据被加载时触发处理函数
+    function onLoadSuccess(data){
+        $.fn.select2.defaults.set( "theme", "bootstrap" );
+        $("select.form-control").each(function () {
+            $(this).select2().on("change", function () {
+                $(this).valid();
+            })
+        })
+        $(".check-box").each(function() {
+            $(this).iCheck({
+                checkboxClass: 'icheckbox-blue'
+            })
+        })
+    }
+
+    // 当拖拽结束后处理函数
+    function onReorderRow(data) {
+        for (var i = 0; i < data.length; i++) {
+            $("#columns_sort_" + data[i].columnId).val(i+1);
+        }
+    }
+
+    $(function() {
+        var tplCategory = $("#tplCategory option:selected").val();
+        tplCategoryVisible(tplCategory);
+    });
+
+    $('#tplCategory').on('select2:select', function (event) {
+        var tplCategory = $(event.target).val();
+        tplCategoryVisible(tplCategory);
+    });
+
+    function tplCategoryVisible(tplCategory) {
+        if("crud" == tplCategory){
+            $("#treeCode").select2("val", [""]);
+            $("#treeParentCode").select2("val", [""]);
+            $("#treeName").select2("val", [""]);
+            $("#otherInfo").addClass("hidden");
+            $("#subInfo").addClass("hidden");
+        } else if("tree" == tplCategory){
+            $("#otherInfo").removeClass("hidden");
+            $("#treeCode").attr("required", "true");
+            $("#treeParentCode").attr("required", "true");
+            $("#treeName").attr("required", "true");
+            $("#subInfo").addClass("hidden");
+        } else if("sub" == tplCategory){
+            $("#subInfo").removeClass("hidden");
+            $("#treeCode").select2("val", [""]);
+            $("#treeParentCode").select2("val", [""]);
+            $("#treeName").select2("val", [""]);
+            $("#otherInfo").addClass("hidden");
+        }
+    }
+
+    // 选择字典处理函数
+    function selectDictTree(columnId, obj) {
+        var dictType = $.common.nullToStr($(obj).parent().find("input").val());
+        var url = ctx + "system/dict/selectDictTree/" + columnId + "/" + dictType;
+        var options = {
+            title: '选择字典类型',
+            width: "380",
+            url: url,
+            callBack: doSubmit
+        };
+        $.modal.openOptions(options);
+    }
+
+    function doSubmit(index, layero){
+        var body = layer.getChildFrame('body', index);
+        var columnId = body.find('#columnId').val();
+        var dictType = body.find('#dictType').val();
+        layer.close(index);
+        $("#columns_dict_" + columnId).val(dictType);
+    }
+</script>
+</body>
+</html>
+
+
+<!-- java类型 -->
+<script id="javaTypeTpl" type="text/x-jquery-tmpl">
+<div>
+<select class='form-control' name='columns[${index}].javaType'>
+    <option value="Long" {{if javaType==="Long"}}selected{{/if}}>Long</option>
+    <option value="String" {{if javaType==="String"}}selected{{/if}}>String</option>
+    <option value="Integer" {{if javaType==="Integer"}}selected{{/if}}>Integer</option>
+    <option value="Double" {{if javaType==="Double"}}selected{{/if}}>Double</option>
+    <option value="BigDecimal" {{if javaType==="BigDecimal"}}selected{{/if}}>BigDecimal</option>
+    <option value="Date" {{if javaType==="Date"}}selected{{/if}}>Date</option>
+</select>
+</div>
+</script>
+
+<!-- 查询方式 -->
+<script id="queryTypeTpl" type="text/x-jquery-tmpl">
+<div>
+<select class='form-control' name='columns[${index}].queryType'>
+    <option value="EQ" {{if queryType==="EQ"}}selected{{/if}}>=</option>
+    <option value="NE" {{if queryType==="NE"}}selected{{/if}}>!=</option>
+    <option value="GT" {{if queryType==="GT"}}selected{{/if}}>></option>
+    <option value="GTE" {{if queryType==="GTE"}}selected{{/if}}>>=</option>
+    <option value="LT" {{if queryType==="LT"}}selected{{/if}}><</option>
+    <option value="LTE" {{if queryType==="LTE"}}selected{{/if}}><=</option>
+    <option value="LIKE" {{if queryType==="LIKE"}}selected{{/if}}>Like</option>
+</select>
+</div>
+</script>
+
+<!-- 显示类型 -->
+<script id="htmlTypeTpl" type="text/x-jquery-tmpl">
+<div>
+<select class='form-control' name='columns[${index}].htmlType'>
+    <option value="input" {{if htmlType==="input"}}selected{{/if}}>文本框</option>
+    <option value="textarea" {{if htmlType==="textarea"}}selected{{/if}}>文本域</option>
+    <option value="select" {{if htmlType==="select"}}selected{{/if}}>下拉框</option>
+    <option value="radio" {{if htmlType==="radio"}}selected{{/if}}>单选框</option>
+    <option value="checkbox" {{if htmlType==="checkbox"}}selected{{/if}}>复选框</option>
+    <option value="datetime" {{if htmlType==="datetime"}}selected{{/if}}>日期控件</option>
+</select>
+</div>
+</script>
diff --git a/src/main/resources/templates/tool/gen/gen.html b/src/main/resources/templates/tool/gen/gen.html
index e1f95ed..d6ddc22 100644
--- a/src/main/resources/templates/tool/gen/gen.html
+++ b/src/main/resources/templates/tool/gen/gen.html
@@ -1,116 +1,195 @@
 <!DOCTYPE html>
 <html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
-<meta charset="utf-8">
 <head th:include="include :: header"></head>
 <body class="gray-bg">
-    
-    <div class="container-div">
-		<div class="row">
-			<div class="col-sm-12 select-info">
-				<form id="gen-form">
-					<div class="select-list">
-						<ul>
-							<li>
-								表名称:<input type="text" name="tableName"/>
-							</li>
-							<li>
-								表描述:<input type="text" name="tableComment"/>
-							</li>
-							<li class="time">
-								<label>表创建时间: </label>
-								<input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[beginTime]"/>
-								<span>-</span>
-								<input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[endTime]"/>
-							</li>
-							<li>
-								<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
-							</li>
-						</ul>
-					</div>
-				</form>
-			</div>
-			
-			<!--<div class="btn-group hidden-xs" id="toolbar" role="group">
-				<a class="btn btn-outline btn-success btn-rounded" onclick="javascript:batchGenCode()" shiro:hasPermission="tool:gen:code">
-			        <i class="fa fa-download"></i> 批量生成
-			    </a>
-		    </div>-->
-		
-		    <div class="col-sm-12 select-info">
-			    <table id="bootstrap-table" data-mobile-responsive="true" class="table table-bordered table-hover"></table>
-			</div>
+<div class="container-div">
+	<div class="row">
+		<div class="col-sm-12 select-info">
+			<form id="gen-form">
+				<div class="select-list">
+					<ul>
+						<li>
+							表名称:<input type="text" name="tableName"/>
+						</li>
+						<li>
+							表描述:<input type="text" name="tableComment"/>
+						</li>
+						<li class="time">
+							<label>表时间: </label>
+							<input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[beginTime]"/>
+							<span>-</span>
+							<input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[endTime]"/>
+						</li>
+						<li>
+							<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+							<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+						</li>
+					</ul>
+				</div>
+			</form>
+		</div>
+
+		<div class="btn-group-sm" id="toolbar" role="group">
+			<a class="btn btn-success multiple disabled" onclick="javascript:batchGenCode()" shiro:hasPermission="tool:gen:code">
+				<i class="fa fa-download"></i> 生成
+			</a>
+			<a class="btn btn-info" onclick="importTable()">
+				<i class="fa fa-upload"></i> 导入
+			</a>
+			<a class="btn btn-primary single disabled" onclick="edit()" shiro:hasPermission="tool:gen:edit">
+				<i class="fa fa-edit"></i> 修改
+			</a>
+			<a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="tool:gen:remove">
+				<i class="fa fa-remove"></i> 删除
+			</a>
+		</div>
+
+		<div class="col-sm-12 select-info">
+			<table id="bootstrap-table"></table>
 		</div>
 	</div>
-	<div th:include="include :: footer"></div>
-	<script type="text/javascript">
-		var prefix = ctx + "tool/gen"
-	
-		$(function() {
-		    var options = {
-		        url: prefix + "/list",
-		        sortName: "CREATE_TIME",
-		        sortOrder: "desc",
-		        search: false,
-		        columns: [{
-		            checkbox: true
-		        },
-		        {
-		            field: 'tableName',
-		            title: '表名称',
-		            width: '20%',
-		            sortable: true
-		        },
-		        {
-		            field: 'tableComment',
-		            title: '表描述',
-		            width: '20%',
-		            sortable: true
-		        },
-		        {
-		            field: 'createTime',
-		            title: '创建时间',
-		            width: '20%',
-		            sortable: true
-		        },
-		        {
-		            field: 'updateTime',
-		            title: '更新时间',
-		            width: '20%',
-		            sortable: true
-		        },
-		        {
-		            title: '操作',
-		            width: '20%',
-		            align: 'center',
-		            formatter: function(value, row, index) {
-		                var msg = '<a class="btn btn-primary btn-xs" href="#" onclick="genCode(\'' + row.tableName + '\')"><i class="fa fa-bug"></i>生成代码</a> ';
-		                return msg;
-		            }
-		        }]
-		    };
-		    $.table.init(options);
+</div>
+<div th:include="include :: footer"></div>
+<script th:inline="javascript">
+	var prefix = ctx + "tool/gen";
+	var editFlag = [[${@permission.hasPermi('tool:gen:edit')}]];
+	var removeFlag = [[${@permission.hasPermi('tool:gen:remove')}]];
+	var previewFlag = [[${@permission.hasPermi('tool:gen:preview')}]];
+	var codeFlag = [[${@permission.hasPermi('tool:gen:code')}]];
+
+	$(function() {
+		var options = {
+			url: prefix + "/list",
+			updateUrl: prefix + "/edit/{id}",
+			removeUrl: prefix + "/remove",
+			sortName: "create_time",
+			sortOrder: "desc",
+			showExport: true,
+			modalName: "生成配置",
+			rememberSelected: true,
+			uniqueId: "tableId",
+			columns: [{
+				field: 'state',
+				checkbox: true
+			},
+				{
+					field: 'tableId',
+					title: '编号',
+					visible: false
+				},
+				{
+					title: "序号",
+					formatter: function (value, row, index) {
+						return $.table.serialNumber(index);
+					}
+				},
+				{
+					field: 'tableName',
+					title: '表名称',
+					sortable: true,
+					formatter: function(value, row, index) {
+						return $.table.tooltip(value);
+					}
+				},
+				{
+					field: 'tableComment',
+					title: '表描述',
+					sortable: true,
+					formatter: function(value, row, index) {
+						return $.table.tooltip(value, 15);
+					}
+				},
+				{
+					field: 'className',
+					title: '实体类名称',
+					sortable: true
+				},
+				{
+					field: 'createTime',
+					title: '创建时间',
+					sortable: true
+				},
+				{
+					field: 'updateTime',
+					title: '更新时间',
+					sortable: true
+				},
+				{
+					title: '操作',
+					align: 'center',
+					formatter: function(value, row, index) {
+						var actions = [];
+						actions.push('<a class="btn btn-info btn-xs ' + previewFlag + '" href="javascript:void(0)" onclick="preview(\'' + row.tableId + '\')"><i class="fa fa-search"></i>预览</a> ');
+						actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.tableId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
+						actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.tableId + '\')"><i class="fa fa-remove"></i>删除</a> ');
+						actions.push('<a class="btn btn-primary btn-xs ' + codeFlag + '" href="javascript:void(0)" onclick="genCode(\'' + row.tableName + '\')"><i class="fa fa-bug"></i>生成代码</a> ');
+						return actions.join('');
+					}
+				}]
+		};
+		$.table.init(options);
+	});
+
+	// 预览代码
+	function preview(tableId) {
+		var preViewUrl = prefix + "/preview/" + tableId;
+		$.modal.loading("正在加载数据,请稍后...");
+		$.get(preViewUrl, function(result) {
+			if (result.code == web_status.SUCCESS) {
+				var items = [];
+				$.each(result.data, function(index, value) {
+					value = value.replace(/</g, "&lt;");
+					value = value.replace(/>/g, "&gt;");
+					var templateName = index.substring(index.lastIndexOf("/") + 1, index.length).replace(/\.vm/g, "");
+					if(!$.common.equals("sql", templateName) && !$.common.equals("tree.html", templateName) && !$.common.equals("sub-domain.java", templateName)){
+						items.push({
+							title: templateName , content: "<pre class=\"layui-code\">" + value + "</pre>"
+						})
+					}
+				});
+				layer.tab({
+					area: ['90%', '90%'],
+					shadeClose: true,
+					tab: items
+				});
+			} else {
+				$.modal.alertError(result.msg);
+			}
+			$.modal.closeLoading();
 		});
-	
-		// 生成代码
-		function genCode(tableName) {
-		    $.modal.confirm("确定要生成" + tableName + "表代码吗?", function() {
-		        location.href = prefix + "/genCode/" + tableName;
-		        layer.msg('执行成功,正在生成代码请稍后…', { icon: 1 });
-		    })
+	}
+
+	// 生成代码
+	function genCode(tableName) {
+		$.modal.confirm("确定要生成" + tableName + "表代码吗?", function() {
+			location.href = prefix + "/genCode/" + tableName;
+			layer.msg('执行成功,正在生成代码请稍后…', { icon: 1 });
+		})
+	}
+
+	//批量生成代码
+	function batchGenCode() {
+		var rows = $.table.selectColumns("tableName");
+		if (rows.length == 0) {
+			$.modal.alertWarning("请选择要生成的数据");
+			return;
 		}
-	
-		//批量生成代码
-		function batchGenCode() {
-		    var rows = $.table.selectColumns("tableName");
-		    if (rows.length == 0) {
-		        $.modal.alertWarning("请选择要生成的数据");
-		        return;
-		    }
-		    $.modal.confirm("确认要生成选中的" + rows.length + "条数据吗?", function() {
-		        location.href = prefix + "/batchGenCode?tables=" + rows;
-		        layer.msg('执行成功,正在生成代码请稍后…', { icon: 1 });
-		    });
-		}
-	</script>
+		$.modal.confirm("确认要生成选中的" + rows.length + "条数据吗?", function() {
+			location.href = prefix + "/batchGenCode?tables=" + rows;
+			layer.msg('执行成功,正在生成代码请稍后…', { icon: 1 });
+		});
+	}
+
+	// 导入表结构
+	function importTable() {
+		var importTableUrl = prefix + "/importTable";
+		$.modal.open("导入表结构", importTableUrl);
+	}
+
+	function edit() {
+		let row = $('#bootstrap-table').bootstrapTable('getSelections');
+		$.operate.edit(row[0].tableId);
+	}
+</script>
 </body>
 </html>
\ No newline at end of file
diff --git a/src/main/resources/templates/tool/gen/importTable.html b/src/main/resources/templates/tool/gen/importTable.html
new file mode 100644
index 0000000..1f50e46
--- /dev/null
+++ b/src/main/resources/templates/tool/gen/importTable.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <th:block th:include="include :: header" />
+</head>
+<body class="gray-bg">
+<div class="container-div">
+    <div class="row">
+        <div class="col-sm-12 search-collapse">
+            <form id="gen-form">
+                <div class="select-list">
+                    <ul>
+                        <li>
+                            表名称:<input type="text" name="tableName"/>
+                        </li>
+                        <li>
+                            表描述:<input type="text" name="tableComment"/>
+                        </li>
+                        <li>
+                            <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+                            <a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+                        </li>
+                    </ul>
+                </div>
+            </form>
+        </div>
+
+        <div class="col-sm-12 select-table table-striped">
+            <table id="bootstrap-table"></table>
+        </div>
+
+        <div class="layui-layer-btn layui-layer-btn-"><a class="layui-layer-btn0" onclick="submitHandler()">确定</a></div>
+    </div>
+</div>
+<th:block th:include="include :: footer" />
+<script type="text/javascript">
+    var prefix = ctx + "tool/gen";
+
+    $(function() {
+        var options = {
+            url: prefix + "/db/list",
+            showSearch: false,
+            showRefresh: false,
+            showToggle: false,
+            showColumns: false,
+            clickToSelect: true,
+            rememberSelected: true,
+            uniqueId: "tableName",
+            columns: [{
+                field: 'state',
+                checkbox: true
+            },
+                {
+                    title: "序号",
+                    formatter: function (value, row, index) {
+                        return $.table.serialNumber(index);
+                    }
+                },
+                {
+                    field: 'tableName',
+                    title: '表名称',
+                    width: '20%',
+                    sortable: true
+                },
+                {
+                    field: 'tableComment',
+                    title: '表描述',
+                    width: '20%',
+                    sortable: true
+                },
+                {
+                    field: 'createTime',
+                    title: '创建时间',
+                    width: '20%',
+                    sortable: true
+                },
+                {
+                    field: 'updateTime',
+                    title: '更新时间',
+                    width: '20%',
+                    sortable: true
+                }]
+        };
+        $.table.init(options);
+    });
+
+    /* 导入表结构-选择表结构-提交 */
+    function submitHandler() {
+        var rows = $.table.selectColumns("tableName");
+        if (rows.length == 0) {
+            $.modal.alertWarning("请至少选择一条记录");
+            return;
+        }
+        var data = {"tables": rows.join()};
+        $.operate.save(prefix + "/importTable", data);
+    }
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/vm/html/add.html.vm b/src/main/resources/templates/vm/html/add.html.vm
index 2a9ad95..e0b201e 100644
--- a/src/main/resources/templates/vm/html/add.html.vm
+++ b/src/main/resources/templates/vm/html/add.html.vm
@@ -1,41 +1,290 @@
 <!DOCTYPE HTML>
-<html  lang="zh" xmlns:th="http://www.thymeleaf.org">
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
 <meta charset="utf-8">
-<head th:include="include :: header"></head>
+<head>
+    <th:block th:include="include :: header"/>
+    #foreach($column in $columns)
+        #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "datetime")
+            <th:block th:include="include :: datetimepicker-css"/>
+            #break
+        #end
+    #end
+</head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-${classname}-add">
-#foreach($column in $columns)
-#if($column.columnName != $primaryKey.columnName)
-			<div class="form-group">	
-				<label class="col-sm-3 control-label">${column.columnComment}:</label>
-				<div class="col-sm-8">
-					<input id="${column.attrname}" name="${column.attrname}" class="form-control" type="text">
-				</div>
-			</div>
-#end
-#end
-			<div class="form-group">
-				<div class="form-control-static col-sm-offset-9">
-					<button type="submit" class="btn btn-primary">提交</button>
-					<button onclick="$.modal.close()" class="btn btn-danger" type="button">关闭</button>
-				</div>
-			</div>
-		</form>
-	</div>
-    <div th:include="include::footer"></div>
-    <script type="text/javascript">
-		var prefix = ctx + "${moduleName}/${classname}"
-		$("#form-${classname}-add").validate({
-			rules:{
-				xxxx:{
-					required:true,
-				},
-			},
-			submitHandler: function(form) {
-				$.operate.save(prefix + "/add", $('#form-${classname}-add').serialize());
-			}
-		});
-	</script>
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-${businessName}-add">
+        #if($table.sub)
+            <h4 class="form-header h4">${functionName}信息</h4>
+        #end
+        #foreach($column in $columns)
+            #set($field=$column.javaField)
+            #if($column.insert && !$column.pk)
+                #if(($column.usableColumn) || (!$column.superColumn))
+                    #set($parentheseIndex=$column.columnComment.indexOf("("))
+                    #if($parentheseIndex != -1)
+                        #set($comment=$column.columnComment.substring(0, $parentheseIndex))
+                    #else
+                        #set($comment=$column.columnComment)
+                    #end
+                    #set($dictType=$column.dictType)
+                    #if("" != $treeParentCode && $column.javaField == $treeParentCode)
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <div class="input-group">
+                                    #set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)})
+                                    #set($treeId = "${className}?.${treeCode}")
+                                    <input id="treeId" name="${treeParentCode}" type="hidden" th:value="${${treeId}}"/>
+                                    <input class="form-control" type="text" onclick="select${BusinessName}Tree()"
+                                           id="treeName" readonly="true" th:value="${${treeName}}"#if($column.required)
+                                           required#end>
+                                    <span class="input-group-addon"><i class="fa fa-search"></i></span>
+                                </div>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "input")
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label#if($column.required) is-required#end">${comment}
+                                :</label>
+                            <div class="col-sm-8">
+                                <input name="${field}" class="form-control" type="text"#if($column.required)
+                                       required#end>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "select" && "" != $dictType)
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <select name="${field}" class="form-control m-b"
+                                        th:with="type=${@dict.getType('${dictType}')}"#if($column.required)
+                                        required#end>
+                                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}"
+                                            th:value="${dict.dictValue}"></option>
+                                </select>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "select" && $dictType)
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <select name="${field}" class="form-control m-b"#if($column.required) required#end>
+                                    <option value="">所有</option>
+                                </select>
+                                <span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 代码生成请选择字典属性</span>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "checkbox" && "" != $dictType)
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8" th:with="type=${@dict.getType('${dictType}')}">
+                                <label th:each="dict : ${type}" class="check-box">
+                                    <input name="${field}" type="checkbox" th:value="${dict.dictValue}"
+                                           th:text="${dict.dictLabel}"#if($column.required) required#end>
+                                </label>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "checkbox" && $dictType)
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <label class="check-box">
+                                    <input name="${field}" type="checkbox"#if($column.required) required#end> 无
+                                </label>
+                                <span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 代码生成请选择字典属性</span>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "radio" && "" != $dictType)
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <div class="radio-box" th:each="dict : ${@dict.getType('${dictType}')}">
+                                    <input type="radio" th:id="${'${field}_' + dict.dictCode}" name="${field}"
+                                           th:value="${dict.dictValue}"
+                                           th:checked="${dict.default}"#if($column.required) required#end>
+                                    <label th:for="${'${field}_' + dict.dictCode}" th:text="${dict.dictLabel}"></label>
+                                </div>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "radio" && $dictType)
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <div class="radio-box">
+                                    <input type="radio" name="${field}" value=""#if($column.required) required#end>
+                                    <label th:for="${field}" th:text="未知"></label>
+                                </div>
+                                <span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 代码生成请选择字典属性</span>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "datetime")
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <div class="input-group date">
+                                    <input name="${field}" class="form-control" placeholder="yyyy-MM-dd"
+                                           type="text"#if($column.required) required#end>
+                                    <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                                </div>
+                            </div>
+                        </div>
+                    #elseif($column.htmlType == "textarea")
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">${comment}:</label>
+                            <div class="col-sm-8">
+                                <textarea name="${field}" class="form-control"#if($column.required)
+                                          required#end></textarea>
+                            </div>
+                        </div>
+                    #end
+                #end
+            #end
+        #end
+        <div class="form-group">
+            <div class="form-control-static col-sm-offset-9">
+                <button type="submit" class="btn btn-primary">提交</button>
+                <button onclick="$.modal.close()" class="btn btn-danger" type="button">关闭</button>
+            </div>
+        </div>
+    </form>
+</div>
+<th:block th:include="include :: footer"/>
+    #foreach($column in $columns)
+        #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "datetime")
+        <th:block th:include="include :: datetimepicker-js"/>
+            #break
+        #end
+    #end
+<script type="text/javascript">
+    var prefix = ctx + "${moduleName}/${businessName}"
+        #if($table.sub)
+            #foreach($column in $subTable.columns)
+                #if(${column.dictType} != '')
+                var ${column.javaField}Datas = [[${@dict.getType('${column.dictType}')}]];
+                #end
+            #end
+        #end
+    $("#form-${businessName}-add").validate({
+        focusCleanup: true
+    });
+
+    function submitHandler() {
+        if ($.validate.form()) {
+            $.operate.save(prefix + "/add", $('#form-${businessName}-add').serialize());
+        }
+    }
+        #foreach($column in $columns)
+            #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "datetime")
+
+            $("input[name='$column.javaField']").datetimepicker({
+                format: "yyyy-mm-dd",
+                minView: "month",
+                autoclose: true
+            });
+            #end
+        #end
+        #if($table.tree)
+
+        /*${functionName}-新增-选择父部门树*/
+        function select${BusinessName}Tree() {
+            var options = {
+                title: '${functionName}选择',
+                width: "380",
+                url: prefix + "/select${BusinessName}Tree/" + $("#treeId").val(),
+                callBack: doSubmit
+            };
+            $.modal.openOptions(options);
+        }
+
+        function doSubmit(index, layero) {
+            var body = layer.getChildFrame('body', index);
+            $("#treeId").val(body.find('#treeId').val());
+            $("#treeName").val(body.find('#treeName').val());
+            layer.close(index);
+        }
+        #end
+        #if($table.sub)
+
+        $(function () {
+            var options = {
+                pagination: false,
+                showSearch: false,
+                showRefresh: false,
+                showToggle: false,
+                showColumns: false,
+            	columns: [{
+                    checkbox: true
+                },
+                {
+                    field: 'index',
+                    align: 'center',
+                    title: "序号",
+                    formatter: function (value, row, index) {
+                        var columnIndex = $.common.sprintf("<input type='hidden' name='index' value='%s'>", $.table.serialNumber(index));
+                        return columnIndex + $.table.serialNumber(index);
+                    }
+                },
+                #foreach($column in $subTable.columns)
+                    #set($dictType=$column.dictType)
+                    #set($javaField=$column.javaField)
+                    #set($parentheseIndex=$column.columnComment.indexOf("("))
+                    #if($parentheseIndex != -1)
+                        #set($comment=$column.columnComment.substring(0, $parentheseIndex))
+                    #else
+                        #set($comment=$column.columnComment)
+                    #end
+                    #if($column.pk || $javaField == ${subTableFkclassName})
+                    #elseif($column.list && "" != $dictType)
+                    {
+                        field: '${javaField}',
+                        align: 'center',
+                        title: '${comment}',
+                        formatter: function (value, row, index) {
+                            var name = $.common.sprintf("${subclassName}List[%s].${javaField}", index);
+                            return $.common.dictToSelect(${javaField}Datas, value, name);
+                        }
+                        #if($velocityCount != $subTable.columns.size())},#end
+
+                    #else
+                    {
+                        field: '${javaField}',
+                        align: 'center',
+                        title: '${comment}',
+                        formatter: function (value, row, index) {
+                            var html = $.common.sprintf("<input class='form-control' type='text' name='${subclassName}List[%s].${javaField}' value='%s'>", index, value);
+                            return html;
+                        }
+                        #if($velocityCount != $subTable.columns.size())},#end
+
+                    #end
+                #end
+            }
+        ]
+        };
+        $.table.init(options);
+        })
+        ;
+
+        function addColumn() {
+            var count = $("#" + table.options.id).bootstrapTable('getData').length;
+            sub.editColumn();
+
+            $("#" + table.options.id).bootstrapTable('insertRow', {
+                index: count,
+                row: {
+                    index: $.table.serialNumber(count),
+            #foreach($column in $subTable.columns)
+                #set($javaField=$column.javaField)
+                #if($column.pk || $javaField == ${subTableFkclassName})
+                #else
+                    ${javaField}: ""#if($velocityCount != $subTable.columns.size()),#end
+
+                #end
+            #end
+        }
+        })
+            ;
+        }
+        #end
+</script>
 </body>
 </html>
diff --git a/src/main/resources/templates/vm/html/edit.html.vm b/src/main/resources/templates/vm/html/edit.html.vm
index eda77a2..ba936b8 100644
--- a/src/main/resources/templates/vm/html/edit.html.vm
+++ b/src/main/resources/templates/vm/html/edit.html.vm
@@ -1,42 +1,281 @@
-<!DOCTYPE HTML>
-<html  lang="zh" xmlns:th="http://www.thymeleaf.org">
-<meta charset="utf-8">
-<head th:include="include :: header"></head>
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
+<head>
+	<th:block th:include="include :: header" />
+	#foreach($column in $columns)
+		#if($column.edit && !$column.superColumn && !$column.pk && $column.htmlType == "datetime")
+			<th:block th:include="include :: datetimepicker-css" />
+			#break
+		#end
+	#end
+</head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-${classname}-edit" th:object="${${classname}}">
-            <input id="${primaryKey.attrname}" name="${primaryKey.attrname}" th:field="*{${primaryKey.attrname}}"  type="hidden">
-#foreach($column in $columns)
-#if($column.columnName != $primaryKey.columnName)
-            <div class="form-group">	
-                <label class="col-sm-3 control-label">${column.columnComment}:</label>
-                <div class="col-sm-8">
-                    <input id="${column.attrname}" name="${column.attrname}" th:field="*{${column.attrname}}" class="form-control" type="text">
-                </div>
-            </div>
-#end
-#end
-			<div class="form-group">
-				<div class="form-control-static col-sm-offset-9">
-					<button type="submit" class="btn btn-primary">提交</button>
-					<button onclick="$.modal.close()" class="btn btn-danger" type="button">关闭</button>
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+	<form class="form-horizontal m" id="form-${businessName}-edit" th:object="${${className}}">
+		#if($table.sub)
+			<h4 class="form-header h4">${functionName}信息</h4>
+		#end
+		<input name="${pkColumn.javaField}" th:field="*{${pkColumn.javaField}}" type="hidden">
+		#foreach($column in $columns)
+			#if($column.edit && !$column.pk)
+				#if(($column.usableColumn) || (!$column.superColumn))
+					#set($parentheseIndex=$column.columnComment.indexOf("("))
+					#if($parentheseIndex != -1)
+						#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+					#else
+						#set($comment=$column.columnComment)
+					#end
+					#set($field=$column.javaField)
+					#set($dictType=$column.dictType)
+					#if("" != $treeParentCode && $column.javaField == $treeParentCode)
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<div class="input-group">
+									#set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)})
+									<input id="treeId" name="${treeParentCode}" type="hidden" th:field="*{${treeParentCode}}" />
+									<input class="form-control" type="text" onclick="select${BusinessName}Tree()" id="treeName" readonly="true" th:field="*{parentName}"#if($column.required) required#end>
+									<span class="input-group-addon"><i class="fa fa-search"></i></span>
+								</div>
+							</div>
+						</div>
+					#elseif($column.htmlType == "input")
+						<div class="form-group">
+							<label class="col-sm-3 control-label#if($column.required) is-required#end">${comment}:</label>
+							<div class="col-sm-8">
+								<input name="${field}" th:field="*{${field}}" class="form-control" type="text"#if($column.required) required#end>
+							</div>
+						</div>
+					#elseif($column.htmlType == "select" && "" != $dictType)
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<select name="${field}" class="form-control m-b" th:with="type=${@dict.getType('${dictType}')}"#if($column.required) required#end>
+									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{${field}}"></option>
+								</select>
+							</div>
+						</div>
+					#elseif($column.htmlType == "select" && $dictType)
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<select name="${field}" class="form-control m-b"#if($column.required) required#end>
+									<option value="">所有</option>
+								</select>
+								<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 代码生成请选择字典属性</span>
+							</div>
+						</div>
+					#elseif($column.htmlType == "checkbox" && "" != $dictType)
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8" th:with="type=${@dict.getType('${dictType}')}">
+								<label th:each="dict : ${type}" class="check-box">
+									<input name="${field}" type="checkbox" th:value="${dict.dictValue}" th:text="${dict.dictLabel}" th:attr="checked=${${className}.${field}.contains(dict.dictValue)?true:false}"#if($column.required) required#end>
+								</label>
+							</div>
+						</div>
+					#elseif($column.htmlType == "checkbox" && $dictType)
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<label class="check-box">
+									<input name="${field}" type="checkbox"#if($column.required) required#end> 无
+								</label>
+								<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 代码生成请选择字典属性</span>
+							</div>
+						</div>
+					#elseif($column.htmlType == "radio" && "" != $dictType)
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<div class="radio-box" th:each="dict : ${@dict.getType('${dictType}')}">
+									<input type="radio" th:id="${'${field}_' + dict.dictCode}" name="${field}" th:value="${dict.dictValue}" th:field="*{${field}}"#if($column.required) required#end>
+									<label th:for="${'${field}_' + dict.dictCode}" th:text="${dict.dictLabel}"></label>
+								</div>
+							</div>
+						</div>
+					#elseif($column.htmlType == "radio" && $dictType)
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<div class="radio-box">
+									<input type="radio" name="${field}" value=""#if($column.required) required#end>
+									<label th:for="${field}" th:text="未知"></label>
+								</div>
+								<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 代码生成请选择字典属性</span>
+							</div>
+						</div>
+					#elseif($column.htmlType == "datetime")
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<div class="input-group date">
+									<input name="${field}" th:value="${#dates.format(${className}.${field}, 'yyyy-MM-dd')}" class="form-control" placeholder="yyyy-MM-dd" type="text"#if($column.required) required#end>
+									<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+								</div>
+							</div>
+						</div>
+					#elseif($column.htmlType == "textarea")
+						<div class="form-group">
+							<label class="col-sm-3 control-label">${comment}:</label>
+							<div class="col-sm-8">
+								<textarea name="${field}" class="form-control"#if($column.required) required#end>[[*{${field}}]]</textarea>
+							</div>
+						</div>
+					#end
+				#end
+			#end
+		#end
+		#if($table.sub)
+			<h4 class="form-header h4">${subTable.functionName}信息</h4>
+			<div class="row">
+				<div class="col-sm-12">
+					<button type="button" class="btn btn-white btn-sm" onclick="addColumn()"><i class="fa fa-plus"> 增加</i></button>
+					<button type="button" class="btn btn-white btn-sm" onclick="sub.delColumn()"><i class="fa fa-minus"> 删除</i></button>
+					<div class="col-sm-12 select-table table-striped">
+						<table id="bootstrap-table"></table>
+					</div>
 				</div>
 			</div>
-		</form>
-    </div>
-    <div th:include="include::footer"></div>
-    <script type="text/javascript">
-		var prefix = ctx + "${moduleName}/${classname}"
-		$("#form-${classname}-edit").validate({
-			rules:{
-				xxxx:{
-					required:true,
+		#end
+	</form>
+</div>
+<th:block th:include="include :: footer" />
+	#foreach($column in $columns)
+		#if($column.edit && !$column.superColumn && !$column.pk && $column.htmlType == "datetime")
+		<th:block th:include="include :: datetimepicker-js" />
+			#break
+		#end
+	#end
+<script th:inline="javascript">
+	var prefix = ctx + "${moduleName}/${businessName}";
+		#if($table.sub)
+			#foreach($column in $subTable.columns)
+				#if(${column.dictType} != '')
+				var ${column.javaField}Datas = [[${@dict.getType('${column.dictType}')}]];
+				#end
+			#end
+		#end
+	$("#form-${businessName}-edit").validate({
+		focusCleanup: true
+	});
+
+	function submitHandler() {
+		if ($.validate.form()) {
+			$.operate.save(prefix + "/edit", $('#form-${businessName}-edit').serialize());
+		}
+	}
+		#foreach($column in $columns)
+			#if($column.edit && !$column.superColumn && !$column.pk && $column.htmlType == "datetime")
+
+			$("input[name='$column.javaField']").datetimepicker({
+				format: "yyyy-mm-dd",
+				minView: "month",
+				autoclose: true
+			});
+			#end
+		#end
+		#if($table.tree)
+
+		/*${functionName}-新增-选择父部门树*/
+		function select${BusinessName}Tree() {
+			var options = {
+				title: '${functionName}选择',
+				width: "380",
+				url: prefix + "/select${BusinessName}Tree/" + $("#treeId").val(),
+				callBack: doSubmit
+			};
+			$.modal.openOptions(options);
+		}
+
+		function doSubmit(index, layero){
+			var body = layer.getChildFrame('body', index);
+			$("#treeId").val(body.find('#treeId').val());
+			$("#treeName").val(body.find('#treeName').val());
+			layer.close(index);
+		}
+		#end
+		#if($table.sub)
+
+		$(function() {
+			var options = {
+				data: [[${${className}.${subclassName}List}]],
+				pagination: false,
+				showSearch: false,
+				showRefresh: false,
+				showToggle: false,
+				showColumns: false,
+			columns: [{
+					checkbox: true
 				},
-			},
-			submitHandler: function(form) {
-				$.operate.save(prefix + "/edit", $('#form-${classname}-edit').serialize());
-			}
+				{
+					field: 'index',
+					align: 'center',
+					title: "序号",
+					formatter: function (value, row, index) {
+						var columnIndex = $.common.sprintf("<input type='hidden' name='index' value='%s'>", $.table.serialNumber(index));
+						return columnIndex + $.table.serialNumber(index);
+					}
+				},
+				#foreach($column in $subTable.columns)
+					#set($dictType=$column.dictType)
+					#set($javaField=$column.javaField)
+					#set($parentheseIndex=$column.columnComment.indexOf("("))
+					#if($parentheseIndex != -1)
+						#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+					#else
+						#set($comment=$column.columnComment)
+					#end
+					#if($column.pk || $javaField == ${subTableFkclassName})
+					#elseif($column.list && "" != $dictType)
+					{
+						field: '${javaField}',
+						align: 'center',
+						title: '${comment}',
+						formatter: function(value, row, index) {
+							var name = $.common.sprintf("${subclassName}List[%s].${javaField}", index);
+							return $.common.dictToSelect(${javaField}Datas, value, name);
+						}
+						#if($velocityCount != $subTable.columns.size())},#end
+
+					#else
+					{
+						field: '${javaField}',
+						align: 'center',
+						title: '${comment}',
+						formatter: function(value, row, index) {
+							var html = $.common.sprintf("<input class='form-control' type='text' name='${subclassName}List[%s].${javaField}' value='%s'>", index, value);
+							return html;
+						}
+						#if($velocityCount != $subTable.columns.size())},#end
+
+					#end
+				#end
+			}]
+		};
+		$.table.init(options);
+		});
+
+		function addColumn() {
+			var count = $("#" + table.options.id).bootstrapTable('getData').length;
+			sub.editColumn();
+
+			$("#" + table.options.id).bootstrapTable('insertRow', {
+				index: count,
+				row: {
+					index: $.table.serialNumber(count),
+			#foreach($column in $subTable.columns)
+				#set($javaField=$column.javaField)
+				#if($column.pk || $javaField == ${subTableFkclassName})
+				#else
+					${javaField}: ""#if($velocityCount != $subTable.columns.size()),#end
+
+				#end
+			#end
+		}
 		});
-	</script>
+		}
+		#end
+</script>
 </body>
-</html>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/vm/html/list.html.vm b/src/main/resources/templates/vm/html/list.html.vm
index a26bd5a..7240764 100644
--- a/src/main/resources/templates/vm/html/list.html.vm
+++ b/src/main/resources/templates/vm/html/list.html.vm
@@ -4,11 +4,69 @@
 <head th:include="include :: header"></head>
 <body class="gray-bg">
     <div class="container-div">
+		<div class="row">
+			<div class="col-sm-12 select-info">
+				<form id="formId">
+					<div class="select-list">
+						<ul>
+							#foreach($column in $columns)
+								#if($column.query)
+									#set($dictType=$column.dictType)
+									#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+									#set($parentheseIndex=$column.columnComment.indexOf("("))
+									#if($parentheseIndex != -1)
+										#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+									#else
+										#set($comment=$column.columnComment)
+									#end
+									#if($column.htmlType == "input")
+										<li>
+											<label>${comment}:</label>
+											<input type="text" name="${column.javaField}"/>
+										</li>
+									#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+										<li>
+											<label>${comment}:</label>
+											<select name="${column.javaField}" th:with="type=${@dict.getType('${dictType}')}">
+												<option value="">所有</option>
+												<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+											</select>
+										</li>
+									#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+										<li>
+											<label>${comment}:</label>
+											<select name="${column.javaField}">
+												<option value="">所有</option>
+												<option value="-1">代码生成请选择字典属性</option>
+											</select>
+										</li>
+									#elseif($column.htmlType == "datetime")
+										<li class="select-time">
+											<label>${comment}:</label>
+											<input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[begin${AttrName}]"/>
+											<span>-</span>
+											<input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[end${AttrName}]"/>
+										</li>
+									#end
+								#end
+							#end
+							<li>
+								<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+								<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+							</li>
+						</ul>
+					</div>
+				</form>
+			</div>
+		</div>
 		<div class="btn-group hidden-xs" id="toolbar" role="group">
-			<a class="btn btn-outline btn-success btn-rounded" onclick="$.operate.add()" shiro:hasPermission="${moduleName}:${classname}:add">
+			<a class="btn btn-outline btn-success btn-rounded" onclick="$.operate.add()" shiro:hasPermission="${moduleName}:${className}:add">
 				<i class="fa fa-plus"></i> 新增
 			</a>
-			<a class="btn btn-outline btn-danger btn-rounded" onclick="$.operate.batRemove()" shiro:hasPermission="${moduleName}:${classname}:remove">
+			<a class="btn btn-outline btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="${permissionPrefix}:edit">
+				<i class="fa fa-edit"></i> 修改
+			</a>
+			<a class="btn btn-outline btn-danger btn-rounded multiple disabled" onclick="$.operate.batRemove()" shiro:hasPermission="${moduleName}:${className}:remove">
 				<i class="fa fa-trash-o"></i> 删除
 			</a>
 		</div>
@@ -19,9 +77,9 @@
     </div>
     <div th:include="include :: footer"></div>
     <script th:inline="javascript">
-        var editFlag = [[${@permission.hasPermi('${moduleName}:${classname}:edit')}]];
-        var removeFlag = [[${@permission.hasPermi('${moduleName}:${classname}:remove')}]];
-        var prefix = ctx + "${moduleName}/${classname}"
+        var editFlag = [[${@permission.hasPermi('${moduleName}:${className}:edit')}]];
+        var removeFlag = [[${@permission.hasPermi('${moduleName}:${className}:remove')}]];
+        var prefix = ctx + "${moduleName}/${className}"
 
         $(function() {
             var options = {
@@ -29,16 +87,37 @@
                 createUrl: prefix + "/add",
                 updateUrl: prefix + "/edit/{id}",
                 removeUrl: prefix + "/remove",
-                modalName: "${tableComment}",
+                modalName: "${functionName}",
                 columns: [{
 		            checkbox: true
 		        },
-#foreach($column in $columns)
-				{
-					field : '${column.attrname}', 
-					title : '${column.columnComment}' 
-				},
-#end
+				#foreach($column in $columns)
+					#set($dictType=$column.dictType)
+					#set($javaField=$column.javaField)
+					#set($parentheseIndex=$column.columnComment.indexOf("("))
+					#if($parentheseIndex != -1)
+						#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+					#else
+						#set($comment=$column.columnComment)
+					#end
+					#if($column.pk){
+							field: '${javaField}',
+							title: '${comment}',
+							visible: false
+						},
+					#elseif($column.list && "" != $dictType){
+							field: '${javaField}',
+							title: '${comment}',
+							formatter: function(value, row, index) {
+								return $.table.selectDictLabel#if($column.htmlType == "checkbox")s#end(${javaField}Datas, value);
+							}
+						},
+					#elseif($column.list && "" != $javaField){
+							field: '${javaField}',
+							title: '${comment}'
+						},
+					#end
+				#end
 		        {
 		            title: '操作',
 		            align: 'center',
diff --git a/src/main/resources/templates/vm/java/Controller.java.vm b/src/main/resources/templates/vm/java/Controller.java.vm
index 16d8919..c1b46ee 100644
--- a/src/main/resources/templates/vm/java/Controller.java.vm
+++ b/src/main/resources/templates/vm/java/Controller.java.vm
@@ -1,7 +1,8 @@
-package ${package}.controller;
+package ${packageName}.controller;
 
 import com.baomidou.mybatisplus.mapper.EntityWrapper;
 import com.baomidou.mybatisplus.plugins.Page;
+import com.huaheng.common.utils.StringUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -13,103 +14,157 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 import com.huaheng.framework.aspectj.lang.annotation.Log;
 import com.huaheng.framework.aspectj.lang.constant.BusinessType;
-import ${package}.domain.${className};
-import ${package}.service.I${className}Service;
+import ${packageName}.domain.${ClassName};
+import ${packageName}.service.I${ClassName}Service;
 import com.huaheng.framework.web.controller.BaseController;
 import com.huaheng.framework.web.domain.AjaxResult;
 import com.huaheng.framework.web.page.TableDataInfo;
+
+import javax.annotation.Resource;
 import java.util.List;
 
 /**
- * ${tableComment} 信息操作处理
- * 
+ * ${functionName} 信息操作处理
+ *
  * @author ${author}
  * @date ${datetime}
  */
 @Controller
-@RequestMapping("/${moduleName}/${classname}")
-public class ${className}Controller extends BaseController
-{
+@RequestMapping("/${moduleName}/${className}")
+public class ${ClassName}Controller extends BaseController {
     private String prefix = "${moduleName}/${classname}";
-	
-	@Autowired
-	private I${className}Service ${classname}Service;
-	
-	@RequiresPermissions("${moduleName}:${classname}:view")
+
+	@Resource
+	private I${ClassName}Service ${className}Service;
+
+	@RequiresPermissions("${moduleName}:${className}:view")
 	@GetMapping()
-	public String ${classname}()
-	{
-	    return prefix + "/${classname}";
+	public String ${className}() {
+	    return prefix + "/${className}";
 	}
-	
+
 	/**
-	 * 查询${tableComment}列表
+	 * 查询${functionName}列表
 	 */
-	@RequiresPermissions("${moduleName}:${classname}:list")
+	@RequiresPermissions("${moduleName}:${className}:list")
 	@PostMapping("/list")
 	@ResponseBody
-	public TableDataInfo list(${className} ${classname})
-	{
-        Page<${className}> page = getPage();
-        EntityWrapper<${className}> entityWrapper = new EntityWrapper();
-        ${classname}Service.selectPage(page, entityWrapper) ;
-		return getDataTable(list);
+	public TableDataInfo list(${ClassName} ${className}) {
+		LambdaQueryWrapper<${ClassName}> lambdaQueryWrapper = Wrappers.lambdaQuery();
+		lambdaQueryWrapper
+		#foreach( ${column} in ${columns})
+			#if(${column.isQuery} == 1)
+				#if(${column.queryType} == "EQ")
+					#if(${column.javaType} == "String")
+							.eq(StringUtils.isNotEmpty(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+						#else
+							.eq(StringUtils.isNotNull(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#end
+				#elseif(${column.queryType} == "NE")
+					#if(${column.javaType} == "String")
+						.ne(StringUtils.isNotEmpty(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#else
+						.ne(StringUtils.isNotNull(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#end
+				#elseif(${column.queryType} == "GT")
+					#if(${column.javaType} == "String")
+						.gt(StringUtils.isNotEmpty(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#else
+						.gt(StringUtils.isNotNull(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#end
+				#elseif(${column.queryType} == "GTE")
+					#if(${column.javaType} == "String")
+						.ge(StringUtils.isNotEmpty(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#else
+						.ge(StringUtils.isNotNull(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#end
+				#elseif(${column.queryType} == "LT")
+					#if(${column.javaType} == "String")
+						.lt(StringUtils.isNotEmpty(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#else
+						.lt(StringUtils.isNotNull(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#end
+				#elseif(${column.queryType} == "LTE")
+					.#if(${column.javaType} == "String")
+						.le(StringUtils.isNotEmpty(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#else
+						.le(StringUtils.isNotNull(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#end
+				#elseif(${column.queryType} == "LIKE")
+					#if(${column.javaType} == "String")
+						.like(StringUtils.isNotEmpty(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#else
+						.like(StringUtils.isNotNull(${className}.get${column.CapJavaField}), ${ClassName}::get${column.CapJavaField}, ${className}.get${column.CapJavaField})
+					#end
+				#end
+			#end
+		#end
+		PageDomain pageDomain = TableSupport.buildPageRequest();
+		Integer pageNum = pageDomain.getPageNum();
+		Integer pageSize = pageDomain.getPageSize();
+		if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)){
+			/*使用分页查询*/
+			Page<${ClassName}> page = new Page<>(pageNum, pageSize);
+			IPage<${ClassName}> iPage = addressService.page(page, lambdaQueryWrapper);
+			return getMpDataTable(iPage.getRecords(), iPage.getTotal());
+		} else {
+			List<${ClassName}> list = ${className}Service.list(lambdaQueryWrapper);
+			return getDataTable(list);
+		}
 	}
-	
+
 	/**
-	 * 新增${tableComment}
+	 * 新增${functionName}
 	 */
 	@GetMapping("/add")
-	public String add()
-	{
+	public String add() {
 	    return prefix + "/add";
 	}
-	
+
 	/**
-	 * 新增保存${tableComment}
+	 * 新增保存${functionName}
 	 */
-	@RequiresPermissions("${moduleName}:${classname}:add")
-	@Log(title = "${tableComment}", action = BusinessType.INSERT)
+	@RequiresPermissions("${moduleName}:${className}:add")
+	@Log(title = "${functionName}", action = BusinessType.INSERT)
 	@PostMapping("/add")
 	@ResponseBody
-	public AjaxResult addSave(${className} ${classname})
-	{		
-		return toAjax(${classname}Service.insert(${classname}));
+	public AjaxResult addSave(${ClassName} ${className}) {
+		return toAjax(${className}Service.insert(${className}));
 	}
 
 	/**
-	 * 修改${tableComment}
+	 * 修改${functionName}
 	 */
-	@GetMapping("/edit/{${primaryKey.attrname}}")
-	public String edit(@PathVariable("${primaryKey.attrname}") ${primaryKey.attrType} ${primaryKey.attrname}, ModelMap mmap)
-	{
-		${className} ${classname} = ${classname}Service.selectEntityById(${primaryKey.attrname});
-		mmap.put("${classname}", ${classname});
+	@GetMapping("/edit/{${pkColumn.javaField}}")
+	public String edit(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}, ModelMap mmap) {
+		${ClassName} ${className} = ${className}Service.getById(${pkColumn.javaField});
+		mmap.put("${className}", ${className});
 	    return prefix + "/edit";
 	}
-	
+
 	/**
-	 * 修改保存${tableComment}
+	 * 修改保存${functionName}
 	 */
-	@RequiresPermissions("${moduleName}:${classname}:edit")
-	@Log(title = "${tableComment}", action = BusinessType.UPDATE)
+	@RequiresPermissions("${moduleName}:${className}:edit")
+	@Log(title = "${functionName}", action = BusinessType.UPDATE)
 	@PostMapping("/edit")
 	@ResponseBody
-	public AjaxResult editSave(${className} ${classname})
-	{		
-		return toAjax(${classname}Service.updateByModel(${classname}));
+	public AjaxResult editSave(${ClassName} ${className}) {
+		return toAjax(${classname}Service.updateById(${classname}));
 	}
-	
+
 	/**
-	 * 删除${tableComment}
+	 * 删除${functionName}
 	 */
-	@RequiresPermissions("${moduleName}:${classname}:remove")
+	@RequiresPermissions("${moduleName}:${className}:remove")
 	@Log(title = "${tableComment}", action = BusinessType.DELETE)
 	@PostMapping( "/remove")
 	@ResponseBody
-	public AjaxResult remove(String ids)
-	{		
-		return toAjax(${classname}Service.deleteById(ids));
+	public AjaxResult remove(String ids) {
+		if (StringUtils.isEmpty(ids)){
+			return AjaxResult.error("id不能为空");
+		}
+		return toAjax(${className}Service.removeByIds(Arrays.asList(Convert.toIntArray(ids))));
 	}
-	
+
 }
diff --git a/src/main/resources/templates/vm/java/Mapper.java.vm b/src/main/resources/templates/vm/java/Mapper.java.vm
index c4f72a2..0358368 100644
--- a/src/main/resources/templates/vm/java/Mapper.java.vm
+++ b/src/main/resources/templates/vm/java/Mapper.java.vm
@@ -1,16 +1,16 @@
-package ${package}.mapper;
-
-import ${package}.domain.${className};
+package ${packageName}.mapper;
 
+import ${packageName}.domain.${ClassName};
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import java.util.List;
 
 /**
- * ${tableComment} 数据层
+ * ${functionName} 数据层
  * 
  * @author ${author}
  * @date ${datetime}
  */
-public interface ${className}Mapper extends BaseMapper<${className}> {
+public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> {
 
 }
 ##	/**
@@ -20,7 +20,7 @@ public interface ${className}Mapper extends BaseMapper<${className}> {
 ##     * @return ${tableComment}信息
 ##     */
 ##	public ${className} select${className}ById(${primaryKey.attrType} ${primaryKey.attrname});
-##
+##7
 ##	/**
 ##     * 查询${tableComment}列表
 ##     *
diff --git a/src/main/resources/templates/vm/java/Service.java.vm b/src/main/resources/templates/vm/java/Service.java.vm
index 891d423..357c5c2 100644
--- a/src/main/resources/templates/vm/java/Service.java.vm
+++ b/src/main/resources/templates/vm/java/Service.java.vm
@@ -1,15 +1,15 @@
-package ${package}.service;
+package ${packageName}.service;
 
-import ${package}.domain.${className};
+import ${packageName}.domain.${ClassName};
 import java.util.List;
 
 /**
- * ${tableComment} 服务层
+ * ${functionName} 服务层
  * 
  * @author ${author}
  * @date ${datetime}
  */
-public interface I${className}Service  extends AutoService<${className}> {
+public interface I${ClassName}Service  extends IService<${ClassName}> {
 
 }
 ##	/**
diff --git a/src/main/resources/templates/vm/java/ServiceImpl.java.vm b/src/main/resources/templates/vm/java/ServiceImpl.java.vm
index a13bc80..966d79a 100644
--- a/src/main/resources/templates/vm/java/ServiceImpl.java.vm
+++ b/src/main/resources/templates/vm/java/ServiceImpl.java.vm
@@ -1,19 +1,19 @@
-package ${package}.service;
+package ${packageName}.service;
 
 import com.baomidou.mybatisplus.service.impl.ServiceImpl;
-import ${package}.domain.${className};
-import ${package}.mapper.${className}Mapper;
+import ${packageName}.domain.${ClassName};
+import ${packageName}.mapper.${ClassName}Mapper;
 import org.springframework.stereotype.Service;
 
 
 /**
- * ${tableComment} 服务层实现
+ * ${functionName} 服务层实现
  * 
  * @author ${author}
  * @date ${datetime}
  */
 @Service
-public class ${className}ServiceImpl extends ServiceImpl<${className}Mapper, ${className}> implements I${className}Service {
+public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}> implements I${ClassName}Service {
 
 }
 ##	@Autowired
diff --git a/src/main/resources/templates/vm/java/domain.java.vm b/src/main/resources/templates/vm/java/domain.java.vm
index 4dab0af..f8ba837 100644
--- a/src/main/resources/templates/vm/java/domain.java.vm
+++ b/src/main/resources/templates/vm/java/domain.java.vm
@@ -1,8 +1,11 @@
-package ${package}.domain;
+package ${packageName}.domain;
 
 import com.baomidou.mybatisplus.activerecord.Model;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotations.TableName;
 import com.baomidou.mybatisplus.annotations.TableField;
+import lombok.Data;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import io.swagger.annotations.ApiModelProperty;
@@ -11,50 +14,29 @@ import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.Date;
 
-#foreach ($column in $columns)
-#if($column.attrType == 'Date')
-    #break
-#end
-#end
-
 /**
- * ${tableComment}表 ${tableName}
+ * ${functionName}表 ${tableName}
  * 
  * @author ${author}
  * @date ${datetime}
  */
 #if ($tableName.contains("_"))
-@TableName("${tableName}")
+@TableName(value = "${tableName}")
+@Data
 #end
-public class ${className} extends Model<${className}>
-{
+public class ${ClassName} implements Serializable{
 	private static final long serialVersionUID = 1L;
 	
 #foreach ($column in $columns)
-	/** $column.columnComment */
-	private $column.attrType $column.attrname;
+        /** $column.columnComment */
+    #if(${column.isPk} == 1 && ${column.isIncrement} == 1)
+    @TableId(value = ${column.javaField}, type = IdType.AUTO)
+    #elseif (${column.isPk} == 1)
+    @TableId(value = ${column.javaField}, type = IdType.INPUT)
+        #else
+    @TableName(value = ${column.javaField})
+    #end
+	private $column.javaType $column.javaField;
 #end
 
-#foreach ($column in $columns)
-	public void set${column.attrName}($column.attrType $column.attrname) 
-	{
-		this.$column.attrname = $column.attrname;
-	}
-
-	public $column.attrType get${column.attrName}() 
-	{
-		return $column.attrname;
-	}
-#end
-
-    @Override
-    protected Serializable pkVal() { return this.id;}
-
-    public String toString() {
-        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
-#foreach ($column in $columns)
-            .append("${column.attrname}", get${column.attrName}())
-#end
-            .toString();
-    }
 }
diff --git a/src/main/resources/templates/vm/xml/Mapper.xml.vm b/src/main/resources/templates/vm/xml/Mapper.xml.vm
index 860479a..5cc1035 100644
--- a/src/main/resources/templates/vm/xml/Mapper.xml.vm
+++ b/src/main/resources/templates/vm/xml/Mapper.xml.vm
@@ -4,15 +4,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="${package}.mapper.${className}Mapper">
     
-##    <resultMap type="${className}" id="${className}Result">
-###foreach ($column in $columns)
-##        <result property="${column.attrname}"    column="${column.columnName}"    />
-###end
-##    </resultMap>
+    <resultMap type="${className}" id="${className}Result">
+#foreach ($column in $columns)
+        <result property="${column.attrname}"    column="${column.columnName}"    />
+#end
+    </resultMap>
 ##
-##	<sql id="select${className}Vo">
-##        select#foreach($column in $columns) $column.columnName#if($velocityCount != $columns.size()),#end#end from ${tableName}
-##    </sql>
+	<sql id="select${className}Vo">
+        select#foreach($column in $columns) $column.columnName#if($velocityCount != $columns.size()),#end#end from ${tableName}
+    </sql>
 ##
 ##    <select id="select${className}List" parameterType="${className}" resultMap="${className}Result">
 ##        <include refid="select${className}Vo"/>