diff --git a/pom.xml b/pom.xml
index a9c147fa..322358cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,12 +126,12 @@
commons-fileupload
commons-fileupload
1.3.1
-
+
diff --git a/src/main/java/egovframework/com/cmm/service/EgovProperties.java b/src/main/java/egovframework/com/cmm/service/EgovProperties.java
new file mode 100644
index 00000000..56b208da
--- /dev/null
+++ b/src/main/java/egovframework/com/cmm/service/EgovProperties.java
@@ -0,0 +1,222 @@
+package egovframework.com.cmm.service;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import egovframework.com.cmm.EgovWebUtil;
+import egovframework.com.cmm.util.EgovResourceCloseHelper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class Name : EgovProperties.java
+ * Description : properties값들을 파일로부터 읽어와 Globals클래스의 정적변수로 로드시켜주는 클래스로
+ * 문자열 정보 기준으로 사용할 전역변수를 시스템 재시작으로 반영할 수 있도록 한다.
+ * Modification Information
+ *
+ * 수정일 수정자 수정내용
+ * ---------- -------- ---------------------------
+ * 2009.01.19 박지욱 최초 생성
+ * 2011.07.20 서준식 Globals파일의 상대경로를 읽은 메서드 추가
+ * 2014.10.13 이기하 Globals.properties 값이 null일 경우 오류처리
+ * 2019.04.26 신용호 RELATIVE_PATH_PREFIX Path 적용 방식 개선
+ * @author 공통 서비스 개발팀 박지욱
+ * @since 2009. 01. 19
+ * @version 1.0
+ * @see
+ *
+ */
+
+public class EgovProperties {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(EgovProperties.class);
+
+ //파일구분자
+ final static String FILE_SEPARATOR = System.getProperty("file.separator");
+
+ //프로퍼티 파일의 물리적 위치
+ //public static final String GLOBALS_PROPERTIES_FILE = System.getProperty("user.home") + FILE_SEPARATOR + "egovProps" +FILE_SEPARATOR + "globals.properties";
+
+ public static final String RELATIVE_PATH_PREFIX = EgovProperties.class.getResource("").getPath().substring(0, EgovProperties.class.getResource("").getPath().lastIndexOf("com"));
+ //public static final String RELATIVE_PATH_PREFIX = EgovProperties.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(0,EgovProperties.class.getProtectionDomain().getCodeSource().getLocation().getPath().indexOf("WEB-INF/classes/")+"WEB-INF/classes/".length())+"egovframework/";
+
+ public static final String GLOBALS_PROPERTIES_FILE = RELATIVE_PATH_PREFIX + "egovProps" + FILE_SEPARATOR + "globals.properties";
+
+ /**
+ * 인자로 주어진 문자열을 Key값으로 하는 상대경로 프로퍼티 값을 절대경로로 반환한다(Globals.java 전용)
+ * @param keyName String
+ * @return String
+ */
+ public static String getPathProperty(String keyName) {
+ String value = "";
+
+ LOGGER.debug("getPathProperty : {} = {}", GLOBALS_PROPERTIES_FILE, keyName);
+
+ FileInputStream fis = null;
+ try {
+ Properties props = new Properties();
+
+ fis = new FileInputStream(EgovWebUtil.filePathBlackList(GLOBALS_PROPERTIES_FILE));
+ props.load(new BufferedInputStream(fis));
+
+ value = props.getProperty(keyName);
+ value = (value == null) ? "" : value.trim();//KISA 보안약점 조치 (2018-10-29, 윤창원)
+ value = RELATIVE_PATH_PREFIX + "egovProps" + System.getProperty("file.separator") + value;
+ } catch (FileNotFoundException fne) {
+ LOGGER.debug("Property file not found.", fne);
+ throw new RuntimeException("Property file not found", fne);
+ } catch (IOException ioe) {
+ LOGGER.debug("Property file IO exception", ioe);
+ throw new RuntimeException("Property file IO exception", ioe);
+ } finally {
+ EgovResourceCloseHelper.close(fis);
+ }
+
+ return value;
+ }
+
+ /**
+ * 인자로 주어진 문자열을 Key값으로 하는 프로퍼티 값을 반환한다(Globals.java 전용)
+ * @param keyName String
+ * @return String
+ */
+ public static String getProperty(String keyName) {
+ String value = "";
+
+ LOGGER.debug("===>>> getProperty"+EgovProperties.class.getProtectionDomain().getCodeSource().getLocation().getPath());
+ LOGGER.debug("getProperty : {} = {}", GLOBALS_PROPERTIES_FILE, keyName);
+
+ FileInputStream fis = null;
+ try {
+ Properties props = new Properties();
+
+ fis = new FileInputStream(EgovWebUtil.filePathBlackList(GLOBALS_PROPERTIES_FILE));
+
+ props.load(new BufferedInputStream(fis));
+ if (props.getProperty(keyName) == null) {
+ return "";
+ }
+ value = props.getProperty(keyName).trim();
+ } catch (FileNotFoundException fne) {
+ LOGGER.debug("Property file not found.", fne);
+ throw new RuntimeException("Property file not found", fne);
+ } catch (IOException ioe) {
+ LOGGER.debug("Property file IO exception", ioe);
+ throw new RuntimeException("Property file IO exception", ioe);
+ } finally {
+ EgovResourceCloseHelper.close(fis);
+ }
+
+ return value;
+ }
+
+ /**
+ * 주어진 파일에서 인자로 주어진 문자열을 Key값으로 하는 프로퍼티 상대 경로값을 절대 경로값으로 반환한다
+ * @param fileName String
+ * @param key String
+ * @return String
+ */
+ public static String getPathProperty(String fileName, String key) {
+ FileInputStream fis = null;
+ try {
+ Properties props = new Properties();
+
+ fis = new FileInputStream(EgovWebUtil.filePathBlackList(fileName));
+ props.load(new BufferedInputStream(fis));
+ fis.close();
+
+ String value = props.getProperty(key);
+ value = RELATIVE_PATH_PREFIX + "egovProps" + System.getProperty("file.separator") + value;
+
+ return value;
+ } catch (FileNotFoundException fne) {
+ LOGGER.debug("Property file not found.", fne);
+ throw new RuntimeException("Property file not found", fne);
+ } catch (IOException ioe) {
+ LOGGER.debug("Property file IO exception", ioe);
+ throw new RuntimeException("Property file IO exception", ioe);
+ } finally {
+ EgovResourceCloseHelper.close(fis);
+ }
+ }
+
+ /**
+ * 주어진 파일에서 인자로 주어진 문자열을 Key값으로 하는 프로퍼티 값을 반환한다
+ * @param fileName String
+ * @param key String
+ * @return String
+ */
+ public static String getProperty(String fileName, String key) {
+ FileInputStream fis = null;
+ try {
+ Properties props = new Properties();
+
+ fis = new FileInputStream(EgovWebUtil.filePathBlackList(fileName));
+ props.load(new BufferedInputStream(fis));
+ fis.close();
+
+ String value = props.getProperty(key);
+
+ return value;
+ } catch (FileNotFoundException fne) {
+ LOGGER.debug("Property file not found.", fne);
+ throw new RuntimeException("Property file not found", fne);
+ } catch (IOException ioe) {
+ LOGGER.debug("Property file IO exception", ioe);
+ throw new RuntimeException("Property file IO exception", ioe);
+ } finally {
+ EgovResourceCloseHelper.close(fis);
+ }
+ }
+
+ /**
+ * 주어진 프로파일의 내용을 파싱하여 (key-value) 형태의 구조체 배열을 반환한다.
+ * @param property String
+ * @return ArrayList
+ */
+ public static ArrayList
")),n+='');var i=z.createElement(n);return"FORM"!==this.element.tagName?(t=z.createElement(''))).appendChild(i):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=t?t:i}},{key:"getExistingFallback",value:function(){function e(e){var t=!0,n=!1,i=void 0;try{for(var r,o=e[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;if(/(^| )fallback($| )/.test(a.className))return a}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}}for(var t=0,n=["div","form"];t".concat(t," ").concat(this.options.dictFileSizeUnits[n])}},{key:"_updateMaxFilesReachedClass",value:function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")}},{key:"drop",value:function(e){if(e.dataTransfer){this.emit("drop",e);for(var t=[],n=0;n1024*this.options.maxFilesize*1024?t(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(e.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):z.isValidFile(e,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(t(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",e)):this.options.accept.call(this,e,t):t(this.options.dictInvalidFileType)}},{key:"addFile",value:function(t){var n=this;t.upload={uuid:z.uuidv4(),progress:0,total:t.size,bytesSent:0,filename:this._renameFile(t)},this.files.push(t),t.status=z.ADDED,this.emit("addedfile",t),this._enqueueThumbnail(t),this.accept(t,function(e){e?(t.accepted=!1,n._errorProcessing([t],e)):(t.accepted=!0,n.options.autoQueue&&n.enqueueFile(t)),n._updateMaxFilesReachedClass()})}},{key:"enqueueFiles",value:function(e){var t=!0,n=!1,i=void 0;try{for(var r,o=e[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;this.enqueueFile(a)}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}return null}},{key:"enqueueFile",value:function(e){var t=this;if(e.status!==z.ADDED||!0!==e.accepted)throw new Error("This file can't be queued because it has already been processed or was rejected.");if(e.status=z.QUEUED,this.options.autoProcessQueue)return setTimeout(function(){return t.processQueue()},0)}},{key:"_enqueueThumbnail",value:function(e){var t=this;if(this.options.createImageThumbnails&&e.type.match(/image.*/)&&e.size<=1024*this.options.maxThumbnailFilesize*1024)return this._thumbnailQueue.push(e),setTimeout(function(){return t._processThumbnailQueue()},0)}},{key:"_processThumbnailQueue",value:function(){var t=this;if(!this._processingThumbnail&&0!==this._thumbnailQueue.length){this._processingThumbnail=!0;var n=this._thumbnailQueue.shift();return this.createThumbnail(n,this.options.thumbnailWidth,this.options.thumbnailHeight,this.options.thumbnailMethod,!0,function(e){return t.emit("thumbnail",n,e),t._processingThumbnail=!1,t._processThumbnailQueue()})}}},{key:"removeFile",value:function(e){if(e.status===z.UPLOADING&&this.cancelUpload(e),this.files=h(this.files,e),this.emit("removedfile",e),0===this.files.length)return this.emit("reset")}},{key:"removeAllFiles",value:function(e){null==e&&(e=!1);var t=!0,n=!1,i=void 0;try{for(var r,o=this.files.slice()[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;a.status===z.UPLOADING&&!e||this.removeFile(a)}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}return null}},{key:"resizeImage",value:function(r,e,t,n,o){var a=this;return this.createThumbnail(r,e,t,n,!0,function(e,t){if(null==t)return o(r);var n=a.options.resizeMimeType;null==n&&(n=r.type);var i=t.toDataURL(n,a.options.resizeQuality);return"image/jpeg"!==n&&"image/jpg"!==n||(i=v.restore(r.dataURL,i)),o(z.dataURItoBlob(i))})}},{key:"createThumbnail",value:function(e,t,n,i,r,o){var a=this,l=new FileReader;l.onload=function(){e.dataURL=l.result,"image/svg+xml"!==e.type?a.createThumbnailFromUrl(e,t,n,i,r,o):null!=o&&o(l.result)},l.readAsDataURL(e)}},{key:"displayExistingFile",value:function(t,e,n,i,r){var o=this,a=!(4u.options.chunkSize),s[0].upload.totalChunkCount=Math.ceil(t.size/u.options.chunkSize)}if(s[0].upload.chunked){var r=s[0],o=e[0];r.upload.chunks=[];function i(){for(var e=0;void 0!==r.upload.chunks[e];)e++;if(!(e>=r.upload.totalChunkCount)){0;var t=e*u.options.chunkSize,n=Math.min(t+u.options.chunkSize,r.size),i={name:u._getParamName(0),data:o.webkitSlice?o.webkitSlice(t,n):o.slice(t,n),filename:r.upload.filename,chunkIndex:e};r.upload.chunks[e]={file:r,index:e,dataBlock:i,status:z.UPLOADING,progress:0,retries:0},u._uploadData(s,[i])}}if(r.upload.finishedChunkUpload=function(e){var t=!0;e.status=z.SUCCESS,e.dataBlock=null,e.xhr=null;for(var n=0;n>1}var s=l/t;return 0==s?1:s}(t);return e.drawImage(t,n,i,r,o,a,l,s,u/c)},v=function(){function e(){u(this,e)}return o(e,null,[{key:"initClass",value:function(){this.KEY_STR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}},{key:"encode64",value:function(e){for(var t="",n=void 0,i=void 0,r="",o=void 0,a=void 0,l=void 0,s="",u=0;o=(n=e[u++])>>2,a=(3&n)<<4|(i=e[u++])>>4,l=(15&i)<<2|(r=e[u++])>>6,s=63&r,isNaN(i)?l=s=64:isNaN(r)&&(s=64),t=t+this.KEY_STR.charAt(o)+this.KEY_STR.charAt(a)+this.KEY_STR.charAt(l)+this.KEY_STR.charAt(s),n=i=r="",o=a=l=s="",ue.length)break}return n}},{key:"decode64",value:function(e){var t=void 0,n=void 0,i="",r=void 0,o=void 0,a="",l=0,s=[];for(/[^A-Za-z0-9\+\/\=]/g.exec(e)&&console.warn("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding."),e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");t=this.KEY_STR.indexOf(e.charAt(l++))<<2|(r=this.KEY_STR.indexOf(e.charAt(l++)))>>4,n=(15&r)<<4|(o=this.KEY_STR.indexOf(e.charAt(l++)))>>2,i=(3&o)<<6|(a=this.KEY_STR.indexOf(e.charAt(l++))),s.push(t),64!==o&&s.push(n),64!==a&&s.push(i),t=n=i="",r=o=a="",l'),this.element.appendChild(e));var l=e.getElementsByTagName("span")[0];return l&&(null!=l.textContent?l.textContent=this.options.dictFallbackMessage:null!=l.innerText&&(l.innerText=this.options.dictFallbackMessage)),this.element.appendChild(this.getFallbackForm())},resize:function(e,t,n,i){var r={srcX:0,srcY:0,srcWidth:e.width,srcHeight:e.height},o=e.width/e.height;null==t&&null==n?(t=r.srcWidth,n=r.srcHeight):null==t?t=n*o:null==n&&(n=t/o);var a=(t=Math.min(t,r.srcWidth))/(n=Math.min(n,r.srcHeight));if(r.srcWidth>t||r.srcHeight>n)if("crop"===i)a\n \n \n
\n
\n \n
\n
\n \n
\n
\n',drop:function(){return this.element.classList.remove("dz-drag-hover")},dragstart:function(){},dragend:function(){return this.element.classList.remove("dz-drag-hover")},dragenter:function(){return this.element.classList.add("dz-drag-hover")},dragover:function(){return this.element.classList.add("dz-drag-hover")},dragleave:function(){return this.element.classList.remove("dz-drag-hover")},paste:function(){},reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(t){var n=this;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){t.previewElement=C.createElement(this.options.previewTemplate.trim()),t.previewTemplate=t.previewElement,this.previewsContainer.appendChild(t.previewElement);var e=!0,i=!1,r=void 0;try{for(var o,a=t.previewElement.querySelectorAll("[data-dz-name]")[Symbol.iterator]();!(e=(o=a.next()).done);e=!0){var l=o.value;l.textContent=t.name}}catch(e){i=!0,r=e}finally{try{e||null==a.return||a.return()}finally{if(i)throw r}}var s=!0,u=!1,c=void 0;try{for(var d,p=t.previewElement.querySelectorAll("[data-dz-size]")[Symbol.iterator]();!(s=(d=p.next()).done);s=!0)(l=d.value).innerHTML=this.filesize(t.size)}catch(e){u=!0,c=e}finally{try{s||null==p.return||p.return()}finally{if(u)throw c}}this.options.addRemoveLinks&&(t._removeLink=C.createElement(''.concat(this.options.dictRemoveFile,"")),t.previewElement.appendChild(t._removeLink));var h=function(e){return e.preventDefault(),e.stopPropagation(),t.status===C.UPLOADING?C.confirm(n.options.dictCancelUploadConfirmation,function(){return n.removeFile(t)}):n.options.dictRemoveFileConfirmation?C.confirm(n.options.dictRemoveFileConfirmation,function(){return n.removeFile(t)}):n.removeFile(t)},f=!0,v=!1,m=void 0;try{for(var y,g=t.previewElement.querySelectorAll("[data-dz-remove]")[Symbol.iterator]();!(f=(y=g.next()).done);f=!0){y.value.addEventListener("click",h)}}catch(e){v=!0,m=e}finally{try{f||null==g.return||g.return()}finally{if(v)throw m}}}},removedfile:function(e){return null!=e.previewElement&&null!=e.previewElement.parentNode&&e.previewElement.parentNode.removeChild(e.previewElement),this._updateMaxFilesReachedClass()},thumbnail:function(e,t){if(e.previewElement){e.previewElement.classList.remove("dz-file-preview");var n=!0,i=!1,r=void 0;try{for(var o,a=e.previewElement.querySelectorAll("[data-dz-thumbnail]")[Symbol.iterator]();!(n=(o=a.next()).done);n=!0){var l=o.value;l.alt=e.name,l.src=t}}catch(e){i=!0,r=e}finally{try{n||null==a.return||a.return()}finally{if(i)throw r}}return setTimeout(function(){return e.previewElement.classList.add("dz-image-preview")},1)}},error:function(e,t){if(e.previewElement){e.previewElement.classList.add("dz-error"),"String"!=typeof t&&t.error&&(t=t.error);var n=!0,i=!1,r=void 0;try{for(var o,a=e.previewElement.querySelectorAll("[data-dz-errormessage]")[Symbol.iterator]();!(n=(o=a.next()).done);n=!0){o.value.textContent=t}}catch(e){i=!0,r=e}finally{try{n||null==a.return||a.return()}finally{if(i)throw r}}}},errormultiple:function(){},processing:function(e){if(e.previewElement&&(e.previewElement.classList.add("dz-processing"),e._removeLink))return e._removeLink.innerHTML=this.options.dictCancelUpload},processingmultiple:function(){},uploadprogress:function(e,t){if(e.previewElement){var n=!0,i=!1,r=void 0;try{for(var o,a=e.previewElement.querySelectorAll("[data-dz-uploadprogress]")[Symbol.iterator]();!(n=(o=a.next()).done);n=!0){var l=o.value;"PROGRESS"===l.nodeName?l.value=t:l.style.width="".concat(t,"%")}}catch(e){i=!0,r=e}finally{try{n||null==a.return||a.return()}finally{if(i)throw r}}}},totaluploadprogress:function(){},sending:function(){},sendingmultiple:function(){},success:function(e){if(e.previewElement)return e.previewElement.classList.add("dz-success")},successmultiple:function(){},canceled:function(e){return this.emit("error",e,this.options.dictUploadCanceled)},canceledmultiple:function(){},complete:function(e){if(e._removeLink&&(e._removeLink.innerHTML=this.options.dictRemoveFile),e.previewElement)return e.previewElement.classList.add("dz-complete")},completemultiple:function(){},maxfilesexceeded:function(){},maxfilesreached:function(){},queuecomplete:function(){},addedfiles:function(){}},this.prototype._thumbnailQueue=[],this.prototype._processingThumbnail=!1}},{key:"extend",value:function(e){for(var t=arguments.length,n=new Array(1"))),this.clickableElements.length){!function l(){return s.hiddenFileInput&&s.hiddenFileInput.parentNode.removeChild(s.hiddenFileInput),s.hiddenFileInput=document.createElement("input"),s.hiddenFileInput.setAttribute("type","file"),(null===s.options.maxFiles||1".concat(this.options.dictFallbackText,"")),n+='');var i=C.createElement(n);return"FORM"!==this.element.tagName?(t=C.createElement(''))).appendChild(i):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=t?t:i}},{key:"getExistingFallback",value:function(){for(var e=function(e){var t=!0,n=!1,i=void 0;try{for(var r,o=e[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;if(/(^| )fallback($| )/.test(a.className))return a}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}},t=0,n=["div","form"];t".concat(t," ").concat(this.options.dictFileSizeUnits[n])}},{key:"_updateMaxFilesReachedClass",value:function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")}},{key:"drop",value:function(e){if(e.dataTransfer){this.emit("drop",e);for(var t=[],n=0;n1024*this.options.maxFilesize*1024?t(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(e.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):C.isValidFile(e,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(t(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",e)):this.options.accept.call(this,e,t):t(this.options.dictInvalidFileType)}},{key:"addFile",value:function(t){var n=this;t.upload={uuid:C.uuidv4(),progress:0,total:t.size,bytesSent:0,filename:this._renameFile(t)},this.files.push(t),t.status=C.ADDED,this.emit("addedfile",t),this._enqueueThumbnail(t),this.accept(t,function(e){e?(t.accepted=!1,n._errorProcessing([t],e)):(t.accepted=!0,n.options.autoQueue&&n.enqueueFile(t)),n._updateMaxFilesReachedClass()})}},{key:"enqueueFiles",value:function(e){var t=!0,n=!1,i=void 0;try{for(var r,o=e[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;this.enqueueFile(a)}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}return null}},{key:"enqueueFile",value:function(e){var t=this;if(e.status!==C.ADDED||!0!==e.accepted)throw new Error("This file can't be queued because it has already been processed or was rejected.");if(e.status=C.QUEUED,this.options.autoProcessQueue)return setTimeout(function(){return t.processQueue()},0)}},{key:"_enqueueThumbnail",value:function(e){var t=this;if(this.options.createImageThumbnails&&e.type.match(/image.*/)&&e.size<=1024*this.options.maxThumbnailFilesize*1024)return this._thumbnailQueue.push(e),setTimeout(function(){return t._processThumbnailQueue()},0)}},{key:"_processThumbnailQueue",value:function(){var t=this;if(!this._processingThumbnail&&0!==this._thumbnailQueue.length){this._processingThumbnail=!0;var n=this._thumbnailQueue.shift();return this.createThumbnail(n,this.options.thumbnailWidth,this.options.thumbnailHeight,this.options.thumbnailMethod,!0,function(e){return t.emit("thumbnail",n,e),t._processingThumbnail=!1,t._processThumbnailQueue()})}}},{key:"removeFile",value:function(e){if(e.status===C.UPLOADING&&this.cancelUpload(e),this.files=without(this.files,e),this.emit("removedfile",e),0===this.files.length)return this.emit("reset")}},{key:"removeAllFiles",value:function(e){null==e&&(e=!1);var t=!0,n=!1,i=void 0;try{for(var r,o=this.files.slice()[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;a.status===C.UPLOADING&&!e||this.removeFile(a)}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}return null}},{key:"resizeImage",value:function(r,e,t,n,o){var a=this;return this.createThumbnail(r,e,t,n,!0,function(e,t){if(null==t)return o(r);var n=a.options.resizeMimeType;null==n&&(n=r.type);var i=t.toDataURL(n,a.options.resizeQuality);return"image/jpeg"!==n&&"image/jpg"!==n||(i=ExifRestore.restore(r.dataURL,i)),o(C.dataURItoBlob(i))})}},{key:"createThumbnail",value:function(e,t,n,i,r,o){var a=this,l=new FileReader;l.onload=function(){e.dataURL=l.result,"image/svg+xml"!==e.type?a.createThumbnailFromUrl(e,t,n,i,r,o):null!=o&&o(l.result)},l.readAsDataURL(e)}},{key:"displayExistingFile",value:function(t,e,n,i,r){var o=this,a=!(4u.options.chunkSize),s[0].upload.totalChunkCount=Math.ceil(t.size/u.options.chunkSize)}if(s[0].upload.chunked){var r=s[0],o=e[0];r.upload.chunks=[];var i=function(){for(var e=0;void 0!==r.upload.chunks[e];)e++;if(!(e>=r.upload.totalChunkCount)){0;var t=e*u.options.chunkSize,n=Math.min(t+u.options.chunkSize,r.size),i={name:u._getParamName(0),data:o.webkitSlice?o.webkitSlice(t,n):o.slice(t,n),filename:r.upload.filename,chunkIndex:e};r.upload.chunks[e]={file:r,index:e,dataBlock:i,status:C.UPLOADING,progress:0,retries:0},u._uploadData(s,[i])}};if(r.upload.finishedChunkUpload=function(e){var t=!0;e.status=C.SUCCESS,e.dataBlock=null,e.xhr=null;for(var n=0;n>1}var s=l/t;return 0==s?1:s},drawImageIOSFix=function(e,t,n,i,r,o,a,l,s,u){var c=detectVerticalSquash(t);return e.drawImage(t,n,i,r,o,a,l,s,u/c)},ExifRestore=function(){function e(){_classCallCheck(this,e)}return _createClass(e,null,[{key:"initClass",value:function(){this.KEY_STR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}},{key:"encode64",value:function(e){for(var t="",n=void 0,i=void 0,r="",o=void 0,a=void 0,l=void 0,s="",u=0;o=(n=e[u++])>>2,a=(3&n)<<4|(i=e[u++])>>4,l=(15&i)<<2|(r=e[u++])>>6,s=63&r,isNaN(i)?l=s=64:isNaN(r)&&(s=64),t=t+this.KEY_STR.charAt(o)+this.KEY_STR.charAt(a)+this.KEY_STR.charAt(l)+this.KEY_STR.charAt(s),n=i=r="",o=a=l=s="",ue.length)break}return n}},{key:"decode64",value:function(e){var t=void 0,n=void 0,i="",r=void 0,o=void 0,a="",l=0,s=[];for(/[^A-Za-z0-9\+\/\=]/g.exec(e)&&console.warn("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding."),e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");t=this.KEY_STR.indexOf(e.charAt(l++))<<2|(r=this.KEY_STR.indexOf(e.charAt(l++)))>>4,n=(15&r)<<4|(o=this.KEY_STR.indexOf(e.charAt(l++)))>>2,i=(3&o)<<6|(a=this.KEY_STR.indexOf(e.charAt(l++))),s.push(t),64!==o&&s.push(n),64!==a&&s.push(i),t=n=i="",r=o=a="",l header {
+ position: relative;
+ padding: 2.8rem 1.4rem;
+ z-index: 10; }
+ body > header .content {
+ opacity: 1;
+ background: #F3F4F5;
+ z-index: 10; }
+ body > header .content > * {
+ max-width: 700px; }
+ body > header .content h1 {
+ margin-bottom: 2.8rem;
+ margin-top: 0; }
+ body > header .content h1 img {
+ max-width: 100%; }
+ body > header .content h1 span {
+ display: none; }
+ @media (min-width: 700px) {
+ body > header #social-buttons {
+ display: inline-block;
+ position: absolute;
+ top: 0.5em;
+ right: 0;
+ opacity: 0.5;
+ -webkit-transition: opacity 0.2s ease;
+ -moz-transition: opacity 0.2s ease;
+ -ms-transition: opacity 0.2s ease;
+ -o-transition: opacity 0.2s ease;
+ transition: opacity 0.2s ease; }
+ body > header #social-buttons:hover {
+ opacity: 1; } }
+ body > header #social-buttons .social-button {
+ display: inline-block; }
+ body > header #social-buttons .social-button.facebook-social-button .fb-like > span {
+ vertical-align: top !important;
+ top: 1px; }
+ body > header .scroll-invitation {
+ margin-top: 2.8rem;
+ margin-bottom: 2.8rem; }
+ body > header .scroll-invitation a {
+ display: block;
+ width: 56px;
+ height: 56px;
+ background: url("../images/arrow.svg") no-repeat; }
+ body > header .scroll-invitation a:hover {
+ text-decoration: none;
+ border: none;
+ background-image: url("../images/arrow-hover.svg"); }
+ body > header .scroll-invitation a span {
+ display: none; }
+ @media (min-width: 700px) {
+ body > header {
+ height: 100vh;
+ margin-bottom: 0; }
+ body > header .content {
+ position: relative;
+ top: 50%;
+ transform: translateY(-50%);
+ -webkit-transform: translateY(-50%);
+ -moz-transform: translateY(-50%); } }
+ @media (min-width: 900px) {
+ body > header {
+ padding-left: 15%; }
+ body > header .content h1 {
+ margin-bottom: 4.2rem; }
+ body > header .content h1 img {
+ width: 550px; }
+ body > header .content h2 {
+ font-size: 1.5em;
+ line-height: 1.4em; } }
+ @media (min-width: 1100px) {
+ body > header {
+ font-size: 1em;
+ line-height: 1.5em; }
+ body > header .content h1 {
+ margin-bottom: 5.6rem; }
+ body > header .content h1 img {
+ width: 700px; }
+ body > header .content > * {
+ max-width: 900px; }
+ body > header h2 {
+ margin-top: 2.8rem;
+ margin-bottom: 2.8rem; }
+ body > header .scroll-invitation {
+ margin-top: 5.6rem; } }
+
+main > nav {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 220px;
+ background: #028AF4;
+ padding: 1.4rem 0;
+ z-index: 200;
+ overflow: auto;
+ display: none; }
+ main > nav.fixed {
+ position: fixed; }
+ main > nav img {
+ margin: 0 0 1.4rem 1.4rem;
+ width: 58px;
+ height: 58px; }
+ main > nav a:not(.logo) {
+ display: block;
+ line-height: 1.4rem;
+ color: rgba(255, 255, 255, 0.9);
+ border: none;
+ padding: 0.7rem 1.4rem;
+ font-size: 0.8rem;
+ -webkit-font-smoothing: subpixel-antialiased; }
+ main > nav a:not(.logo):hover {
+ background: rgba(255, 255, 255, 0.3); }
+ main > nav .sub-sections {
+ height: 0;
+ overflow: hidden;
+ -webkit-transition: height 0.4s ease;
+ -moz-transition: height 0.4s ease;
+ -ms-transition: height 0.4s ease;
+ -o-transition: height 0.4s ease;
+ transition: height 0.4s ease; }
+ main > nav .visible {
+ background: rgba(255, 255, 255, 0.13); }
+ main > nav .visible .sub-sections {
+ display: block; }
+ main > nav a.current {
+ background: #4DADF7; }
+ main > nav .level-0 > a {
+ font-weight: 400; }
+ main > nav .level-1 > a {
+ padding-left: 1.9rem;
+ color: rgba(255, 255, 255, 0.7); }
+
+@media (min-width: 940px) {
+ main {
+ padding-left: 220px; }
+ main > nav {
+ display: block; } }
+form.donate {
+ display: inline-block;
+ vertical-align: bottom;
+ position: relative;
+ top: 0.25em;
+ margin: 0 0em 0 0.2em; }
+
+main {
+ position: relative;
+ z-index: 100; }
+ main section {
+ padding: 1.4rem 1.4rem 2.8rem 1.4rem; }
+ main section:last-of-type {
+ padding-bottom: 8.4rem; }
+ main section h1, main section h2 {
+ margin-top: 0;
+ padding-top: 2.8rem; }
+ main section > * {
+ max-width: 720px;
+ margin-left: auto;
+ margin-right: auto; }
+ main section > *.highlight {
+ max-width: 900px; }
+ main section > table {
+ max-width: 80rem; }
+ main section .video-grid {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 1.5rem; }
+ main section .video-grid .video-grid__cell {
+ display: block;
+ position: relative;
+ flex: 1;
+ margin-left: 1.5rem; }
+ main section .video-grid .video-grid__cell:first-child {
+ margin-left: 0; }
+ main section .video-grid .video-grid__cell:after {
+ content: '';
+ padding-top: 56.25%;
+ display: block; }
+ main section .video-grid .video-grid__cell iframe {
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: 100%; }
+ main section .embedded-video {
+ position: relative;
+ width: 100%; }
+ main section .embedded-video:after {
+ content: '';
+ padding-top: 56.25%;
+ display: block; }
+ main section .embedded-video iframe {
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: 100%; }
+ main section:nth-child(odd) {
+ background: #F3F4F5; }
+ main section:nth-child(even) {
+ background: #E8E9EC; }
+ main section.news {
+ background: #646C7F;
+ color: white; }
+ main section.news h1, main section.news h2 {
+ color: white;
+ -webkit-font-smoothing: subpixel-antialiased; }
+ main section.news a {
+ color: #C0E3FE;
+ border-color: #C0E3FE; }
+ main .configuration-table-container {
+ max-width: 100%;
+ overflow-x: scroll; }
+ main table {
+ font-size: 0.9rem;
+ margin-top: 1.4rem;
+ margin-bottom: 4.2rem;
+ border: 1px solid #38A0FE;
+ border-bottom: none;
+ background: white; }
+ main table th:first-of-type,
+ main table td:first-of-type {
+ text-align: right; }
+ main table th, main table td {
+ text-align: left;
+ border-bottom: 1px solid #38A0FE;
+ padding: 0.7rem 1.4rem; }
+ main table td:first-of-type, main table th:first-of-type {
+ border-right: 1px solid #38A0FE; }
+ main table a.default-value {
+ display: block;
+ font-weight: normal;
+ color: rgba(0, 88, 160, 0.3);
+ font-size: 0.9em; }
+ main table a.default-value:hover {
+ border: none;
+ color: #0087F7; }
+ main table td:first-of-type {
+ font-weight: bold;
+ color: #0087F7; }
+ main table th.title {
+ text-align: center;
+ padding-top: 2.8rem;
+ padding-bottom: 2.8rem; }
+ main table th.title p {
+ margin-bottom: 0; }
+ main table td.separator {
+ font-weight: normal;
+ text-align: left;
+ color: #646C7F; }
+ main table p {
+ margin: 0; }
+ @media (max-width: 600px) {
+ main table table, main table tbody, main table thead, main table tr, main table td, main table th {
+ display: block; }
+ main table a.default-value {
+ display: inline;
+ margin-left: 0.5em; }
+ main table td, main table th {
+ overflow: auto; }
+ main table td:first-of-type, main table th:first-of-type {
+ text-align: left;
+ border-right: none; }
+ main table td.label {
+ border-bottom-color: rgba(0, 135, 247, 0.15); }
+ main table th.title {
+ padding-top: 1.4rem;
+ padding-bottom: 1.4rem; }
+ main table th:not(.title) {
+ display: none; } }
+
+footer {
+ background: #2D3038;
+ z-index: 5000;
+ position: relative;
+ display: block;
+ padding: 1.4rem 1.4rem 2.8rem 1.4rem;
+ font-size: 0.9rem;
+ color: white; }
+ footer * {
+ color: white; }
+ footer a:hover {
+ border-color: white; }
+ footer > * {
+ max-width: 720px;
+ margin-left: auto;
+ margin-right: auto; }
+ @media (min-width: 720px) {
+ footer .license {
+ text-align: justify; } }
+ footer .logo {
+ margin: 2.8rem 0;
+ width: 270px; }
+
+.for-hire {
+ text-align: center;
+ padding: 1em 2em;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 0.3rem;
+ line-height: 1.5em; }
+ .for-hire h1 {
+ padding: 0;
+ margin: 1.5rem 0 3rem; }
+ .for-hire h1 img {
+ max-width: 100%;
+ height: auto; }
diff --git a/src/main/webapp/js/jsgrid/nlib-jsgrid-theme.css b/src/main/webapp/js/jsgrid/nlib-jsgrid-theme.css
new file mode 100644
index 00000000..ebc78552
--- /dev/null
+++ b/src/main/webapp/js/jsgrid/nlib-jsgrid-theme.css
@@ -0,0 +1,258 @@
+/*
+ * jsGrid v1.5.3 (http://js-grid.com)
+ * (c) 2016 Artem Tabalin
+ * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE)
+ */
+
+.jsgrid-grid-header,
+.jsgrid-grid-body,
+.jsgrid-header-row > .jsgrid-header-cell,
+.jsgrid-filter-row > .jsgrid-cell,
+.jsgrid-insert-row > .jsgrid-cell,
+.jsgrid-edit-row > .jsgrid-cell {
+ border: 1px solid #e9e9e9;
+}
+
+.jsgrid-header-row > .jsgrid-header-cell {
+ border-top: 0;
+}
+
+.jsgrid-header-row > .jsgrid-header-cell,
+.jsgrid-filter-row > .jsgrid-cell,
+.jsgrid-insert-row > .jsgrid-cell {
+ border-bottom: 0;
+}
+
+.jsgrid-header-row > .jsgrid-header-cell:first-child,
+.jsgrid-filter-row > .jsgrid-cell:first-child,
+.jsgrid-insert-row > .jsgrid-cell:first-child {
+ border-left: none;
+}
+
+.jsgrid-header-row > .jsgrid-header-cell:last-child,
+.jsgrid-filter-row > .jsgrid-cell:last-child,
+.jsgrid-insert-row > .jsgrid-cell:last-child {
+ border-right: none;
+}
+
+.jsgrid-header-row .jsgrid-align-right,
+.jsgrid-header-row .jsgrid-align-left {
+ text-align: center;
+}
+
+.jsgrid-grid-header {
+ background: #f9f9f9;
+}
+
+.jsgrid-header-scrollbar {
+ scrollbar-arrow-color: #f1f1f1;
+ scrollbar-base-color: #f1f1f1;
+ scrollbar-3dlight-color: #f1f1f1;
+ scrollbar-highlight-color: #f1f1f1;
+ scrollbar-track-color: #f1f1f1;
+ scrollbar-shadow-color: #f1f1f1;
+ scrollbar-dark-shadow-color: #f1f1f1;
+}
+
+.jsgrid-header-scrollbar::-webkit-scrollbar {
+ visibility: hidden;
+}
+
+.jsgrid-header-scrollbar::-webkit-scrollbar-track {
+ background: #f1f1f1;
+}
+
+.jsgrid-header-sortable:hover {
+ cursor: pointer;
+ background: #fcfcfc;
+}
+
+.jsgrid-header-row .jsgrid-header-sort {
+ background: #c4e2ff;
+}
+
+.jsgrid-header-sort:before {
+ content: " ";
+ display: block;
+ float: left;
+ width: 0;
+ height: 0;
+ border-style: solid;
+}
+
+.jsgrid-header-sort-asc:before {
+ border-width: 0 5px 5px 5px;
+ border-color: transparent transparent #009a67 transparent;
+}
+
+.jsgrid-header-sort-desc:before {
+ border-width: 5px 5px 0 5px;
+ border-color: #009a67 transparent transparent transparent;
+}
+
+.jsgrid-grid-body {
+ border-top: none;
+}
+
+.jsgrid-cell {
+ border: #f3f3f3 1px solid;
+}
+
+.jsgrid-grid-body .jsgrid-row:first-child .jsgrid-cell,
+.jsgrid-grid-body .jsgrid-alt-row:first-child .jsgrid-cell {
+ border-top: none;
+}
+
+.jsgrid-grid-body .jsgrid-cell:first-child {
+ border-left: none;
+}
+
+.jsgrid-grid-body .jsgrid-cell:last-child {
+ border-right: none;
+}
+
+.jsgrid-row > .jsgrid-cell {
+ background: #fff;
+}
+
+.jsgrid-alt-row > .jsgrid-cell {
+ background: #fcfcfc;
+}
+
+.jsgrid-header-row > .jsgrid-header-cell {
+ background: #f9f9f9;
+}
+
+.jsgrid-filter-row > .jsgrid-cell {
+ background: #fcfcfc;
+}
+
+.jsgrid-insert-row > .jsgrid-cell {
+ background: #e3ffe5;
+}
+
+.jsgrid-edit-row > .jsgrid-cell {
+ background: #fdffe3;
+}
+
+.jsgrid-selected-row > .jsgrid-cell {
+ background: #c4e2ff;
+ border-color: #c4e2ff;
+}
+
+.jsgrid-nodata-row > .jsgrid-cell {
+ background: #fff;
+}
+
+.jsgrid-invalid input,
+.jsgrid-invalid select,
+.jsgrid-invalid textarea {
+ background: #ffe3e5;
+ border: 1px solid #ff808a;
+}
+
+.jsgrid-pager-current-page {
+ font-weight: bold;
+}
+
+.jsgrid-pager-nav-inactive-button a {
+ color: #d3d3d3;
+}
+
+.jsgrid-button + .jsgrid-button {
+ margin-left: 5px;
+}
+
+.jsgrid-button:hover {
+ opacity: .5;
+ transition: opacity 200ms linear;
+}
+
+.jsgrid .jsgrid-button {
+ width: 16px;
+ height: 16px;
+ border: none;
+ cursor: pointer;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAFgEAYAAADx4WWjAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAZjElEQVR42u2deVyU1f7HzzyzDzCA7MgihCsY7uYOiZIamebVFl/Wy8zSxLTQuld/lmIuCGIu9cruLa9lXlNTUQsVUgQRNBbZF5F9lWEbZpj9/P74doQZ87LMDHjvPe9/vj7MPOf5fp6zjc/3e86D0H8rNTVRUd988/rrxpZTV3f48PHjixb1m+N5eVOmvP76hQtXryKEEMYVFWFhERG7dvW2nIKC4OBVq/71L1JOWdk774SHb9tmcoc1mpaWtjYLi6Ki+fPff/+f/7x2DS6YlobQpEkYJyQgxOViXFGxYUNExN693ZVXWDh//urV339/8yZCQiGUM3EixqTcqqq//e3gwQ8+MJkAnU6tVqvZbKk0KSkj47nnfv/d2nrcOInk1i2EHBw6HSBCyss3btyz53Eh4PiJEwkJCAkEGKenIzRlCsaJiQhZW2OcmentHRhYUCCX5+Tcv+/nZ/KaIKjVlZUSibPz3bu2tv7+jY1JSQjZ22P8++8ITZiA8c2bCIlEGNfU7Njx9dcbN5aXv/fe55/v2kXueFfHrawwzsjw8po1Kz9fp5PJVCoOx9T+sp4spLq6qcnJKTPTzy8wMDtbpWpurqlxcBAIEPL2RkihQKi8HCEWCyEOByGBACEPD4RkMoSysxGyshoxYtKkvLxnn83IiI3192cYoZDL1WjMduefhEbT2Njebm2dnT1x4sKFt26lpCDk7Q01Mm5cp719GyEPD4zz8+fNe/PNixcx1moxZhhz+9ftBTgce3tLy9ZWodDT08enqEirRUguh89YrE6r1SLU3o6QWDx27HPP3bjBYrHZLJZO1+933JDCwnnzVq8+eZJ0xvR0hKZNwzg5GSF3d4xJjXRt+2IxxpWVYWFRUX/964A5npc3e/aKFT/+aNg5k5IQsrPDOC9v/PjXX09Nzcpyc3vhhbKy5GSEXFygSY0fD6MWj4dxRcWmTZGRW7b0m+P5+dOnr1jx00+Gw+HNmwhZWmKcmTliRFBQdjb5vlrd1NTSYm2dmsrnDx0qlycnI+TsrC9EIMC4ouKDDyIitm41ucNqtUTS3GxlVVQ0f/66dZ0TWUYGQjNmQJPo6rhOp1RqtWy2YTkKRUFBVdXQoampPN7QoR0dt26BkPR0hCZPxpjMyDCRbdxoMgE6nUqlVnO5UmlSUmbmtGl374rFY8c2NcXFwQXv3RsxIigoKwtjpVKr7X5UUSgKCiorfXxSU0Wi4cOl0vh4KCcz08srMPD+fbk8O7u4+NlnTV4ThI6OrKzS0qFDc3JCQpYuPX1ap+vo0Ggev+PdIZPduVNY6O+flRUUFBLyyy9arVSqUAiFZnPcEI2mrq611cbG2HJUqrKyhgZX135znEKhUCgUCoVCoVAoFAqFQvlP4FGIyd//wIGYGPL0eOFCsOSRooWF/jHG3RT7R7kkwNHeDpY8W7106d69DRteesn4p9UGQbdZs8BOmAD26FGwajVYS8veFd/RAZbExtatA6tUgjW5gFdfBXv4MFhnZ7hTpEZ6D9TsiRNwFBsL9u23jXWc8FiUEi5IwqFlZWBLSkAIqaGeOn7uHBzNnAl2yBAoRyo1lYDHnvfDBUiV+/qCfeYZcOjs2Z45/s03cBQQAHbUKFM7TmB19wVwyNMTjlJSwCYlgW1tBUtqjM8HGxwMdsYMcDw319SOE7qNuIAD5eVwtGwZ2CVLwFpbgyU1RvrQypXmdrzHAvQhTaCoCOzy5eDoqlVwnJEBtqnJ3I73UQAJEQkEYG1t9T8nf++/UJKRqQBsNvSR3sfQBkiASPTHaX+cV1sLTUirhWNWt4PCAAuorQU7aBDYpUv1P5fJwJo+rcZoAfqjyvvvgz18GJpQXh4cjxkD1vTjvdEC9IUcOwZHZDglwyyEtREqLu4vARQKhUKhUCgUCoVCoVAoFMp/JL1+GAuLe0isbM8esCScum/fhAkIpaWdP9/vAsCxyEg4mjsX7MOHYA2XEE6ZAtbKSv/vJBx786b+eVwuWEdHsHFxINT4xUAGT5G9vMA6O4NtbgZrbw+WPJVOTARLHrMTS+LC48aBbWsD29gI1sFB/zrGYyCAhIpIXJgE927fBktCSCtXgv31V/07v3gx2DVrwA4dCjYwEOzp02DPnDGTAJJSQCLq48eDhdVlCB05Apa0fdKESFxg506wOTlgSVDwtdfAkiaZkAC2vt5YAQaP10kA4949/b8/KcWARGQMAxrk76TJEFJTwVZWGuv4YwKgU5FQEYn/EkjwjrRxQwyTP8ioRM4j1NTAdQzLN4EAfVpa9I9JDSgUYEmb77ogtyvkcxL4Jpg+cvMEASTiQiBtndQQEWIogNQE+Zz0KYLp48dPEFBSon9MhlUS/yXDIhk+iQDSxFQqsGKxfjkkQG52AWT8N1yRTZYmks8N5wHSREjc2MlJ/3zTdd5uBJBF54Zt1s4OLJmgyJ0nAkjNkBwKMgOTpkfOM7sAMkoYdmYyqvzZqvquDhKhBJJy9qRRzIQCYJgjna2qSv9TMk+QpkCakkSi/33iMIFMWGQiMx3dRNQNL0gi825uYEeNAks6rYsL2Dfe0D+PjP9kxu43Afn5YOfNAxsSApYMk6SzkiZE/u7tDZb8GjWc2SkUCoVCoVAoFAqFQqFQKBTjMNuiHViaMmQIHJFNgqOjYQVIRcVTKwAcJ2swydJFEuEhi0sDAvRXCPYdk20pC46TQAiJIxgG/0iN/PILfN/DY8AF6C8WTU8Hu3YtWPIQ2HBNJXko/MMPxl6/z+u9wHGSOhATA5Y8nSZbnhcWgiXLch88AEtibl9/bayAXvcBcHzwYDgiKQfdpQ6Q9cW//AKWy4U+QCL3/SAAHCc5EyRyTxa+9RQfH3DcMIjYd7ptQvqdjaQIkM7YHSQ2tmSJqR3vVgA4TqKMpI331HHCokXg+MWLpnb8iQL0R5UbN/rm+MKF5nacYLAxBgkVVVeDJaNKT1m8GBwnuxmYH7b+cEi21ScB7qlT//3ppI2TO06aWv/xx0Tm7g72wgWwZCOLjz7689NITgS545cu9bfjBI6+Q6TTkt8wpEZIqtknn4B95ZWBuuOG/FEDZC8VAo8HliRnkJ8IU6eC46SmBp4/aoC0ebKnyq1bYElKQWEhOE5+GlAoFAqFQqFQKBQKhUKhUCgDi8nCrKdOFRffuTN+/LJlMTEREdevIyQUkuAqPPPu6JBKEbp6ddmy7dtDQubOdXPz9SUBk75jsk0dWSwWi8Xi8xFiGDbbygpsVwFwzGIhxGIZLpB7CgQAGg1CGMOjYmIJXY/JmhvjMfu7U80NFTDQUAEEnc6w0/6HCfDyEovt7auru442+oJ0Oq0WITc3CwtbW7Kk0Xg4P/10//6dO7D4n8Uiq097/mJkPp/N5nC02t9+q6rKzx89GiGBgIzyMP6zWGB5PKEQoZMni4tTUwMCLl4sLc3MFAg6OrRatbrn2z5DTTMMl8swDKNUshCKilqyBOPOiae3TYE4yGZDdpBAALkoGOvfBhYLFu12dMCaV6iR3l+PnMflCgQdHRyY8lUqEMDj9b5A8m0ixNDxzu/B36EmOq/T272qQQCbzeUKhUolB4oATX3b9rr3Z5Gm1Sm99+fDOVotB35kCYXGNyFyvlBImhD5BrnT0IRUKlhtbFwT0mi0Wo1m0CDWtWuVlbm5s2aBI2TRf8/fJ8/lMgybrVbn5zc319b6+a1Zc/36t98ePtzZJ4jjSqVcjlBExKxZK1Zs3jxpkqOjl9edO2q1TqfVksGjezDGGGMOh81msRhGJuPMmePuboqftZmZjY3l5QUFCKlUCsXhw11HI7jDarVKhVBIiKenv39MzMiRtrYuLsbHnU02D+TmNjXV1np6du2c+m2bYRgGoaoqmay5meRmGI/JBEBT+jPHzQv9LTTQUAEDjcn+Tww/srjczgmKWDJrwjHGCGFM8pGeIgHw61Au5/G4XIGgoYFhuFyBoPPXjk6n0ajVLBZMQP33hggKhUKhUCgUCoVCoVAoFAoFIYTQhx8mJHz//dq1CO3b9/LLLS0IRUQsXNjSsm3b7ds//fThh/3tT6+fzEFekK0t/Ivsbkw+M9ws2Pz04eEuxhgrlX+eF0S2rH2qBTxd9FrAk5I6GKZ/Q0uEXvcBFxdLSxuburquj9ExRsjBgc+3sGho6HcBH32UkHD8eGgoxHOFQmjThlkOGNvYCARCYXt7XFx1dUHBCy8gJBTC1vBQH2fPlpZmZISE7NiRknLmDIvV2qpSKRQWFlBeZ91AnJdhWCyGYRiZLCpq5szly8mbJXoPC6F9+xYuxBghFotE6v8810Gng78LBJBOKRLpj0FyOWx4rlBAJB7Cqo9DymcYCG+HhZ092/fGx3k8v+dJof/HR50/vyxxsOt9//flGANn27bp05cv37wZxnUSWyepBp0vRrCzEwgsLEgTCgq6eLGkJC1twQIiPDjY03PMmCtX5s718Bg16sqVtjalsqNDJAInSV3AkU7H4bBYLBab3d6+fTtCP/9svJAeExWVlnbx4vLlCO3Z8+KLGCO0e/eCBRgfOZKRERv77rv96ArUQG9PKC9va5NInJ07kzmgKdTXKxRSKdmfov/o9TzA4zFM/72F3gwCIEw60G530ut7CeO4QNB1tOkcVQzfO/MUCmCzGYbNbmyEcby1lQhgGIZhsfp/JqZQKBQKhUKhUCgUCoVCofxv0uPAQmNjW1tbm5NTfX1ra3Pzyy9LJFKpVDp9Oiy/cnXV6XQ6na69ncfj8bjcjAw7O0tLK6vz5729nZ1dXDIzzSWg2ydzKSlFRfn5lpbNzTKZVLpwYUtLe3t7+xtvqFQajUYzdqxWi5BOZ2mJMUTN1GqdTqcjb0cXiUpL6+vr6trbvbycnJyd79/vtxqorJRIHj4cOTI7u7y8rOz0aY1Gq9VqfX0h0I0Ql8tmczgPH8Kz0oICCIU4OkIUc/hwEIgQj8fhcDgajY+Pi4uLyzvvDBvm6urmduyYqQQ89nS6rq6lpalpzJi8vMrKioq4OHDI11ck4vEEghs3LCz4fIFgzhxwzNNz3rxx4yZMmDmTx+NyudyRIwUCHo/HGz5cLBaJRKIjR0AuhwM34rvvSkrq6mprN240Ww1cuZKenpaWmalUajQajb+/lZVQKBB8+WVg4OjR/v7vv9/bC1y/np19797LL0ON/PxzR4darVKxWFOnDh8+cuS0aY6O1tY2NsnJfa4BuVyhUCjc3cvLGxrq69evl8tVKqXS318k4vN5vOvX++o4Ac4/fx5q7IMPSHShpKS2trZ23z5ja4ApL29sbGgIDq6qamqSSF56ic2G8CjYnTuNvYC+kEOHBAIul8stK2tr6+iQy6dOLStraKir6+17DLoIaG6WyWSyoCCFQq1Wq6dO5XI5HA6npgbu0+3bphLw6IIMwzDMtWtk1WtDQ0tLS8vEiX0uD9anOzoyDEKdK7rJjt+mzz6BUaywkAReVSqtVqcj7+TogwCtVqfT6eRyksQBw6GDA3xsrnCevb3+XghqdZ8FQGdNT+dwGIZh8vNBkK8vCOnt+we6B2bsqVPBImRjIxKJRH2f4BgvLwcHJ6cLF5ydbW1tbGJjVSqtVqMhaTVhYaZyPD4+MzM9fc4cjQZjjGfOtLQUi8XikhJfXw+PIUN+/bXPAmxtraysrNLS7O3FYmvrL7+EO9/erlSq1Wp1aOj16zk5WVmQUtA3x+/dy8iwsdHptFqtNjqax3N0dHREaNiwIUO8vDZvNvbGPJqJ7eysrMTi+/dHjHBzc3ffvFmhUKlUKoRUKrVarT53Diakdevi47OyMjO73wsF7viMGRhrtVptQoKFxZgxY8b4+ra0XLp0+TJCpaV790ZEGN/Hnvhb6MGDurra2vXrc3MrKysqoqNJ0gaPx2az2ZWVMBxevgyjSlkZhFutraHpzZ4N6SKTJgkEDg4ODghJJBcvXryIUEXFRx9t2oSQTCaXy+UIBQVFRkZGrls3bVpYWFhY7/OGuv05XVvb1NTUNHFicXFtbU3NoUMKhVqtUk2eDD/iyLDY+X2MoXNaWg4aZGf34IG7u6urq2tYWE7O6tXvvhsQcPfusWPHjq1fb23t7e3tjZBUWl1dXY3QvHmHDh06tGHDhAmrV69e/cUXJhNgSFWVRNLY6OtbXS2RSCQTJqjVsEUI5GApldbWIpFIVFDg6enu7uGRlGRpyWKxWJ27msXFbdmyZcvBgykpUVFRUaGhYrG7u7s7Qm1tICQ4ODIyMnLTpokT165duzYyss9ty9xcv/7ZZ599duDAzp1CoVCIcVSUm5ubG8bkOCkpIiIiYseOgfazWxISwsPDw6Ojw8N5PB4P46iowYMHD8Z4506RSCTCODFx9+7du033m8xsgKO7du3cKRAIBBgfPOjj4+PTKeTKlbCwsLC9ewfaz25JSTl06NCh8PDwcD6fz8d49247Ozs7jD/5hM1mszHOyzt79uzZ0FC5vKGhoWHIkKcuc/e550JDQ0P/7/+Cg/fv379/yxadDuYjS0sLCwsLhLhcoVAolEoRYrPZbKVyoP19IgpFa2trq739jz+GhISEnDhx8uTixYsXHz/e0dHc3NxsazvQ/lEoFAqFQqFQKBQKhUKhUChG0U2ERiBob1cqFYp9+ySStjapdP58iGa2tdnbW1uLxTt3jhzp5ubhcfbsUyMgL6+qqqIiKKisrKGhvv7YMUg5GDwY4jFlZbDlu7W1VouxTicUWltbWFhYXL7s7m5nZ2+/dKmHh4ODo2PP319gLI8e7hYV1dRUVQUGlpc3NDQ0XLum1ep0GJeWwtLDCRMglDRsGJvNZjPM4MEcDofD4Xz4YX19S0tz8yuv1NQ0NUkksbH9XgUymULR0WFvD2k29fUxMXfupKQkJvb0/Bs3cnKysvz8zp9PTU1OxhjygvovIMEUF9fV1dZu3QrRRUdHCws+n8/v+crsgAA/v2efzcnhcjkcLvf0aYlEKm1tXbPm/v3a2urqnu9q32cBLS3t7TLZsmWwGrW8fPZsf/+xY/PyelsQZLlcuaLVarUYI1Rd3dTU3Gz+Fd4MQgixWCT3zdLyt9+yszMze79NAkTibW2hHB5PKOTxeDzymngzCvDxcXZ2cgoP1+lg7wKVSqPRalev7m1BGHO5XO6qVba2Hh7u7ikpkyYNHTpsWHOzuQU84s6d4uKCgpiY06eTkxMTMYbOOWpUd+fFxxcW5udv356QUF9fW4txeXlZWWlp3zOwesujplJd3dTU2CgQlJXV1zc0nD/f1NTeLpUGB8OmL6dPw7B57RpE4p2ctFoej8t99VWRaNAgOztf39zc5ctXrECIzy8vLy8/dWrSpC1btmxZvtzff+XKlSt7/kqM3vJoHhg8eNAge3uFYtq0kSNHjXrhBVdXW1s7ux07RCI+n8+fOBEmtM8/hy3KQ0OHDHnmGR+fqioXl4cPJZIXX9Tp0tLS0lJSiopKSkpKli0rKrp69erVc+dyck6dOnWq/9faP0ZiYl5ebq6bW2pqQUF+/uM7OV27tm5daKit7YkTISEhIUlJkEiD8fHjwcHBwcnJKSkHDx482PfUsn7jzp2vvvrqKzu7b78NCAgIyMjYtIkImT179uz4+Nu3o6Ojo21sBtrPbklOjoyMjLS0PH48KCgoKC5u/XoQcvTojBkzZmRkyGR1dXV1gwcbex2z7dExdWpYWFhYe3tVVWpqaurChTKZVCqVPniAsUaj0bi58flisVjc92Q/swsguLlNnjx5skx26dKqVatW/eMfarVGo9HodAzD5/P5Eom5r0+hUCgUCoVCoVAoFAqFQqH8b3PsWHFxauqLLx49mp+fmLho0UD702Nyc1ta6urc3RHas2fBAowR2rkzOBjjTz+9e/fCBeO3ZzDbKiaZTKNRqRgmIOD06e3bb95EiMuFMIdGo1Ih9N13hYXJyatWPbUCnn/+3Lndu2NjHz5sbKyoGDKEOO7q6uHh51dRkZPzl7989tnkyU+dgDfeiI//+9+/+CI1tbDw1q05cxDi80UihBiGz7ew0Gji4195ZevW558Xi/l8oRD2yX8q2LUrMzM29r33EAoPDwrCGKEvvnjtNYwR2rVrwQKMY2OrqwsKnn/ebA6MHn3y5JYtiYlTppw6tW1bz9e4x8RUVOTkzJpFNpFH6MABcBw6a3R0Ts5vv61dazbH58y5dGn//h9+QGjr1mnTOi88evQPP3z88Y0bdXUKhUz2eJQxL6+lpa7OzQ2h6OhXX1WpEIqOXroUY4R27JgzB+OVKxMSjh//6iuzOf4HzPTpzs5Dh2ZmImRt7eSEEEIikViMUHZ2dXVBwaxZfn7ffbd+fV7egwetrQ0NnUsAAwLOnAkPv3ULIY1GqeRyEZLL29oQmjnTzy8wMD7+229nzVqxYs0acwt4FOi+cqWqKi8vIGDRonPndu+OiZHLOzqkUisr8ioKe3t7e0/PsjIPDyurQYNKS9PTy8uzsgIDEVKrlUqEXF3t7T08KisfPHjrrQMHvLwEAjabwyFv6e4HAYQHD6RSicTNbc6cM2d27IiLKympqSksHD4cISsre3uEEMIY3IL9JxBiGDZbpyspefvtI0e8vJ55xsrKzq6iwtyOEx4bRr29razs7KqqcnLefHP//tGj584dOXLmzMuXEWptra8HAbDDgVqtUCD0669Llnz66dy5/e14r3nrrbi4o0ePHkXo44/HjcN4796srGvXNmwYaL96TVxcZWVu7owZA+3Hfw3/D73/bnBl1mLvAAAAAElFTkSuQmCC);
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
+ .jsgrid .jsgrid-button {
+ background-image: url(data:image/png;base64,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);
+ background-size: 24px 352px;
+ }
+}
+
+.jsgrid .jsgrid-mode-button {
+ width: 24px;
+ height: 24px;
+}
+
+.jsgrid-mode-on-button {
+ opacity: .5;
+}
+
+.jsgrid-cancel-edit-button { background-position: 0 0; width: 16px; height: 16px; }
+.jsgrid-clear-filter-button { background-position: 0 -40px; width: 16px; height: 16px; }
+.jsgrid-delete-button { background-position: 0 -80px; width: 16px; height: 16px; }
+.jsgrid-edit-button { background-position: 0 -120px; width: 16px; height: 16px; }
+.jsgrid-insert-mode-button { background-position: 0 -160px; width: 24px; height: 24px; }
+.jsgrid-insert-button { background-position: 0 -208px; width: 16px; height: 16px; }
+.jsgrid-search-mode-button { background-position: 0 -248px; width: 24px; height: 24px; }
+.jsgrid-search-button { background-position: 0 -296px; width: 16px; height: 16px; }
+.jsgrid-update-button { background-position: 0 -336px; width: 16px; height: 16px; }
+
+
+.jsgrid-load-shader {
+ background: #ddd;
+ opacity: .5;
+ filter: alpha(opacity=50);
+}
+
+.jsgrid-load-panel {
+ width: 15em;
+ height: 5em;
+ background: #fff;
+ border: 1px solid #e9e9e9;
+ padding-top: 3em;
+ text-align: center;
+}
+
+.jsgrid-load-panel:before {
+ content: ' ';
+ position: absolute;
+ top: .5em;
+ left: 50%;
+ margin-left: -1em;
+ width: 2em;
+ height: 2em;
+ border: 2px solid #009a67;
+ border-right-color: transparent;
+ border-radius: 50%;
+ -webkit-animation: indicator 1s linear infinite;
+ animation: indicator 1s linear infinite;
+}
+
+@-webkit-keyframes indicator
+{
+ from { -webkit-transform: rotate(0deg); }
+ 50% { -webkit-transform: rotate(180deg); }
+ to { -webkit-transform: rotate(360deg); }
+}
+
+@keyframes indicator
+{
+ from { transform: rotate(0deg); }
+ 50% { transform: rotate(180deg); }
+ to { transform: rotate(360deg); }
+}
+
+/* old IE */
+.jsgrid-load-panel {
+ padding-top: 1.5em\9;
+}
+.jsgrid-load-panel:before {
+ display: none\9;
+}
diff --git a/src/main/webapp/js/jsgrid/nlib-jsgrid.css b/src/main/webapp/js/jsgrid/nlib-jsgrid.css
new file mode 100644
index 00000000..5865cab0
--- /dev/null
+++ b/src/main/webapp/js/jsgrid/nlib-jsgrid.css
@@ -0,0 +1,128 @@
+/*
+ * jsGrid v1.5.3 (http://js-grid.com)
+ * (c) 2016 Artem Tabalin
+ * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE)
+ */
+
+.jsgrid {
+ position: relative;
+ overflow: hidden;
+ font-size: 1em;
+}
+
+.jsgrid, .jsgrid *, .jsgrid *:before, .jsgrid *:after {
+ box-sizing: border-box;
+}
+
+.jsgrid input,
+.jsgrid textarea,
+.jsgrid select {
+ font-size: 1em;
+}
+
+.jsgrid-grid-header {
+ overflow-x: hidden;
+ overflow-y: scroll;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+.jsgrid-grid-body {
+ overflow-x: auto;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+}
+
+.jsgrid-table {
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.jsgrid-cell {
+ padding: 0.5em 0.5em;
+}
+
+.jsgrid-сell,
+.jsgrid-header-cell {
+ box-sizing: border-box;
+}
+
+.jsgrid-align-left {
+ text-align: left;
+}
+
+.jsgrid-align-center,
+.jsgrid-align-center input,
+.jsgrid-align-center textarea,
+.jsgrid-align-center select {
+ text-align: center;
+}
+
+.jsgrid-align-right,
+.jsgrid-align-right input,
+.jsgrid-align-right textarea,
+.jsgrid-align-right select {
+ text-align: right;
+}
+
+.jsgrid-header-cell {
+ padding: .5em .5em;
+}
+
+.jsgrid-filter-row input,
+.jsgrid-filter-row textarea,
+.jsgrid-filter-row select,
+.jsgrid-edit-row input,
+.jsgrid-edit-row textarea,
+.jsgrid-edit-row select,
+.jsgrid-insert-row input,
+.jsgrid-insert-row textarea,
+.jsgrid-insert-row select {
+ width: 100%;
+ padding: .3em .5em;
+}
+
+.jsgrid-filter-row input[type='checkbox'],
+.jsgrid-edit-row input[type='checkbox'],
+.jsgrid-insert-row input[type='checkbox'] {
+ width: auto;
+}
+
+
+.jsgrid-selected-row .jsgrid-cell {
+ cursor: pointer;
+}
+
+.jsgrid-nodata-row .jsgrid-cell {
+ padding: .5em 0;
+ text-align: center;
+}
+
+.jsgrid-header-sort {
+ cursor: pointer;
+}
+
+.jsgrid-pager {
+ padding: .5em 0;
+ padding-top: 20px;
+ text-align: center;
+}
+
+.jsgrid-pager-nav-button {
+ padding: .2em .6em;
+}
+
+.jsgrid-pager-nav-inactive-button {
+ /* display: none; [NLIB:주석처리] */
+ pointer-events: none;
+}
+
+.jsgrid-pager-page {
+ padding: .2em .6em;
+}
diff --git a/src/main/webapp/js/jsgrid/nlib-jsgrid.js b/src/main/webapp/js/jsgrid/nlib-jsgrid.js
new file mode 100644
index 00000000..922bcdf4
--- /dev/null
+++ b/src/main/webapp/js/jsgrid/nlib-jsgrid.js
@@ -0,0 +1,2516 @@
+/*
+ * jsGrid v1.5.3 (http://js-grid.com)
+ * (c) 2016 Artem Tabalin
+ * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE)
+ */
+
+(function(window, $, undefined) {
+
+ var JSGRID = "JSGrid",
+ JSGRID_DATA_KEY = JSGRID,
+ JSGRID_ROW_DATA_KEY = "JSGridItem",
+ JSGRID_EDIT_ROW_DATA_KEY = "JSGridEditRow",
+
+ SORT_ORDER_ASC = "asc",
+ SORT_ORDER_DESC = "desc",
+
+ FIRST_PAGE_PLACEHOLDER = "{first}",
+ PAGES_PLACEHOLDER = "{pages}",
+ PREV_PAGE_PLACEHOLDER = "{prev}",
+ NEXT_PAGE_PLACEHOLDER = "{next}",
+ LAST_PAGE_PLACEHOLDER = "{last}",
+ PAGE_INDEX_PLACEHOLDER = "{pageIndex}",
+ PAGE_COUNT_PLACEHOLDER = "{pageCount}",
+ ITEM_COUNT_PLACEHOLDER = "{itemCount}",
+
+ EMPTY_HREF = "javascript:void(0);";
+
+ var getOrApply = function(value, context) {
+ if($.isFunction(value)) {
+ return value.apply(context, $.makeArray(arguments).slice(2));
+ }
+ return value;
+ };
+
+ var normalizePromise = function(promise) {
+ var d = $.Deferred();
+
+ if(promise && promise.then) {
+ promise.then(function() {
+ d.resolve.apply(d, arguments);
+ }, function() {
+ d.reject.apply(d, arguments);
+ });
+ } else {
+ d.resolve(promise);
+ }
+
+ return d.promise();
+ };
+
+ var defaultController = {
+ loadData: $.noop,
+ insertItem: $.noop,
+ updateItem: $.noop,
+ deleteItem: $.noop
+ };
+
+
+ function Grid(element, config) {
+ var $element = $(element);
+
+ $element.data(JSGRID_DATA_KEY, this);
+
+ this._container = $element;
+
+ this.data = [];
+ this.fields = [];
+
+ this._editingRow = null;
+ this._sortField = null;
+ this._sortOrder = SORT_ORDER_ASC;
+ this._firstDisplayingPage = 1;
+
+ this._init(config);
+ this.render();
+ }
+
+ Grid.prototype = {
+ width: "auto",
+ height: "auto",
+ updateOnResize: true,
+
+ rowClass: $.noop,
+ rowRenderer: null,
+
+ rowClick: function(args) {
+ if(this.editing) {
+ this.editItem($(args.event.target).closest("tr"));
+ }
+ },
+ rowDoubleClick: $.noop,
+
+ noDataContent: "Not found",
+ noDataRowClass: "jsgrid-nodata-row",
+
+ heading: true,
+ headerRowRenderer: null,
+ headerRowClass: "jsgrid-header-row",
+ headerCellClass: "jsgrid-header-cell",
+
+ filtering: false,
+ filterRowRenderer: null,
+ filterRowClass: "jsgrid-filter-row",
+
+ inserting: false,
+ insertRowRenderer: null,
+ insertRowClass: "jsgrid-insert-row",
+
+ editing: false,
+ editRowRenderer: null,
+ editRowClass: "jsgrid-edit-row",
+
+ confirmDeleting: true,
+ deleteConfirm: "Are you sure?",
+
+ selecting: true,
+ selectedRowClass: "jsgrid-selected-row",
+ oddRowClass: "jsgrid-row",
+ evenRowClass: "jsgrid-alt-row",
+ cellClass: "jsgrid-cell",
+
+ sorting: false,
+ sortableClass: "jsgrid-header-sortable",
+ sortAscClass: "jsgrid-header-sort jsgrid-header-sort-asc",
+ sortDescClass: "jsgrid-header-sort jsgrid-header-sort-desc",
+
+ paging: false,
+ pagerContainer: null,
+ pageIndex: 1,
+ pageSize: 20,
+ pageButtonCount: 15,
+ pagerFormat: "Pages: {first} {prev} {pages} {next} {last} {pageIndex} of {pageCount}",
+ pagePrevText: "Prev",
+ pageNextText: "Next",
+ pageFirstText: "First",
+ pageLastText: "Last",
+ pageNavigatorNextText: "...",
+ pageNavigatorPrevText: "...",
+ pagerContainerClass: "jsgrid-pager-container",
+ pagerClass: "jsgrid-pager",
+ pagerNavButtonClass: "jsgrid-pager-nav-button",
+ pagerNavButtonInactiveClass: "jsgrid-pager-nav-inactive-button",
+ pageClass: "jsgrid-pager-page",
+ currentPageClass: "jsgrid-pager-current-page",
+
+ customLoading: false,
+ pageLoading: false,
+
+ autoload: false,
+ controller: defaultController,
+
+ loadIndication: true,
+ loadIndicationDelay: 500,
+ loadMessage: "Please, wait...",
+ loadShading: true,
+
+ invalidMessage: "Invalid data entered!",
+
+ invalidNotify: function(args) {
+ var messages = $.map(args.errors, function(error) {
+ return error.message || null;
+ });
+
+ window.alert([this.invalidMessage].concat(messages).join("\n"));
+ },
+
+ onInit: $.noop,
+ onRefreshing: $.noop,
+ onRefreshed: $.noop,
+ onPageChanged: $.noop,
+ onItemDeleting: $.noop,
+ onItemDeleted: $.noop,
+ onItemInserting: $.noop,
+ onItemInserted: $.noop,
+ onItemEditing: $.noop,
+ onItemUpdating: $.noop,
+ onItemUpdated: $.noop,
+ onItemInvalid: $.noop,
+ onDataLoading: $.noop,
+ onDataLoaded: $.noop,
+ onOptionChanging: $.noop,
+ onOptionChanged: $.noop,
+ onError: $.noop,
+
+ invalidClass: "jsgrid-invalid",
+
+ containerClass: "jsgrid",
+ tableClass: "jsgrid-table",
+ gridHeaderClass: "jsgrid-grid-header",
+ gridBodyClass: "jsgrid-grid-body",
+
+ _init: function(config) {
+ $.extend(this, config);
+ this._initLoadStrategy();
+ this._initController();
+ this._initFields();
+ this._attachWindowLoadResize();
+ this._attachWindowResizeCallback();
+ this._callEventHandler(this.onInit)
+ },
+
+ loadStrategy: function() {
+ return this.pageLoading
+ ? new jsGrid.loadStrategies.PageLoadingStrategy(this)
+ : new jsGrid.loadStrategies.DirectLoadingStrategy(this);
+ },
+
+ _initLoadStrategy: function() {
+ this._loadStrategy = getOrApply(this.loadStrategy, this);
+ },
+
+ _initController: function() {
+ this._controller = $.extend({}, defaultController, getOrApply(this.controller, this));
+ },
+
+ renderTemplate: function(source, context, config) {
+ args = [];
+ for(var key in config) {
+ args.push(config[key]);
+ }
+
+ args.unshift(source, context);
+
+ source = getOrApply.apply(null, args);
+ return (source === undefined || source === null) ? "" : source;
+ },
+
+ loadIndicator: function(config) {
+ return new jsGrid.LoadIndicator(config);
+ },
+
+ validation: function(config) {
+ return jsGrid.Validation && new jsGrid.Validation(config);
+ },
+
+ _initFields: function() {
+ var self = this;
+ self.fields = $.map(self.fields, function(field) {
+ if($.isPlainObject(field)) {
+ var fieldConstructor = (field.type && jsGrid.fields[field.type]) || jsGrid.Field;
+ field = new fieldConstructor(field);
+ }
+ field._grid = self;
+ return field;
+ });
+ },
+
+ _attachWindowLoadResize: function() {
+ $(window).on("load", $.proxy(this._refreshSize, this));
+ },
+
+ _attachWindowResizeCallback: function() {
+ if(this.updateOnResize) {
+ $(window).on("resize", $.proxy(this._refreshSize, this));
+ }
+ },
+
+ _detachWindowResizeCallback: function() {
+ $(window).off("resize", this._refreshSize);
+ },
+
+ option: function(key, value) {
+ var optionChangingEventArgs,
+ optionChangedEventArgs;
+
+ if(arguments.length === 1)
+ return this[key];
+
+ optionChangingEventArgs = {
+ option: key,
+ oldValue: this[key],
+ newValue: value
+ };
+ this._callEventHandler(this.onOptionChanging, optionChangingEventArgs);
+
+ this._handleOptionChange(optionChangingEventArgs.option, optionChangingEventArgs.newValue);
+
+ optionChangedEventArgs = {
+ option: optionChangingEventArgs.option,
+ value: optionChangingEventArgs.newValue
+ };
+ this._callEventHandler(this.onOptionChanged, optionChangedEventArgs);
+ },
+
+ fieldOption: function(field, key, value) {
+ field = this._normalizeField(field);
+
+ if(arguments.length === 2)
+ return field[key];
+
+ field[key] = value;
+ this._renderGrid();
+ },
+
+ _handleOptionChange: function(name, value) {
+ this[name] = value;
+
+ switch(name) {
+ case "width":
+ case "height":
+ this._refreshSize();
+ break;
+ case "rowClass":
+ case "rowRenderer":
+ case "rowClick":
+ case "rowDoubleClick":
+ case "noDataRowClass":
+ case "noDataContent":
+ case "selecting":
+ case "selectedRowClass":
+ case "oddRowClass":
+ case "evenRowClass":
+ this._refreshContent();
+ break;
+ case "pageButtonCount":
+ case "pagerFormat":
+ case "pagePrevText":
+ case "pageNextText":
+ case "pageFirstText":
+ case "pageLastText":
+ case "pageNavigatorNextText":
+ case "pageNavigatorPrevText":
+ case "pagerClass":
+ case "pagerNavButtonClass":
+ case "pageClass":
+ case "currentPageClass":
+ case "pagerRenderer":
+ this._refreshPager();
+ break;
+ case "fields":
+ this._initFields();
+ this.render();
+ break;
+ case "data":
+ case "editing":
+ case "heading":
+ case "filtering":
+ case "inserting":
+ case "paging":
+ this.refresh();
+ break;
+ case "loadStrategy":
+ case "pageLoading":
+ this._initLoadStrategy();
+ this.search();
+ break;
+ case "pageIndex":
+ this.openPage(value);
+ break;
+ case "pageSize":
+ this.refresh();
+ this.search();
+ break;
+ case "editRowRenderer":
+ case "editRowClass":
+ this.cancelEdit();
+ break;
+ case "updateOnResize":
+ this._detachWindowResizeCallback();
+ this._attachWindowResizeCallback();
+ break;
+ case "invalidNotify":
+ case "invalidMessage":
+ break;
+ default:
+ this.render();
+ break;
+ }
+ },
+
+ destroy: function() {
+ this._detachWindowResizeCallback();
+ this._clear();
+ this._container.removeData(JSGRID_DATA_KEY);
+ },
+
+ render: function() {
+ this._renderGrid();
+ return this.autoload ? this.loadData() : $.Deferred().resolve().promise();
+ },
+
+ _renderGrid: function() {
+ this._clear();
+
+ this._container.addClass(this.containerClass)
+ .css("position", "relative")
+ .append(this._createHeader())
+ .append(this._createBody());
+
+ this._pagerContainer = this._createPagerContainer();
+ this._loadIndicator = this._createLoadIndicator();
+ this._validation = this._createValidation();
+
+ this.refresh();
+ },
+
+ _createLoadIndicator: function() {
+ return getOrApply(this.loadIndicator, this, {
+ message: this.loadMessage,
+ shading: this.loadShading,
+ container: this._container
+ });
+ },
+
+ _createValidation: function() {
+ return getOrApply(this.validation, this);
+ },
+
+ _clear: function() {
+ this.cancelEdit();
+
+ clearTimeout(this._loadingTimer);
+
+ this._pagerContainer && this._pagerContainer.empty();
+
+ this._container.empty()
+ .css({ position: "", width: "", height: "" });
+ },
+
+ _createHeader: function() {
+ var $headerRow = this._headerRow = this._createHeaderRow(),
+ $filterRow = this._filterRow = this._createFilterRow(),
+ $insertRow = this._insertRow = this._createInsertRow();
+
+ var $headerGrid = this._headerGrid = $("").addClass(this.tableClass)
+ .append($headerRow)
+ .append($filterRow)
+ .append($insertRow);
+
+ var $header = this._header = $("").addClass(this.gridHeaderClass)
+ .addClass(this._scrollBarWidth() ? "jsgrid-header-scrollbar" : "")
+ .append($headerGrid);
+
+ return $header;
+ },
+
+ _createBody: function() {
+ var $content = this._content = $("
");
+
+ var $bodyGrid = this._bodyGrid = $("").addClass(this.tableClass)
+ .append($content);
+
+ var $body = this._body = $("").addClass(this.gridBodyClass)
+ .append($bodyGrid)
+ .on("scroll", $.proxy(function(e) {
+ this._header.scrollLeft(e.target.scrollLeft);
+ }, this));
+
+ return $body;
+ },
+
+ _createPagerContainer: function() {
+ var pagerContainer = this.pagerContainer || $("
").appendTo(this._container);
+ return $(pagerContainer).addClass(this.pagerContainerClass);
+ },
+
+ _eachField: function(callBack) {
+ var self = this;
+ $.each(this.fields, function(index, field) {
+ if(field.visible) {
+ callBack.call(self, field, index);
+ }
+ });
+ },
+
+ _createHeaderRow: function() {
+ if($.isFunction(this.headerRowRenderer))
+ return $(this.renderTemplate(this.headerRowRenderer, this));
+
+ var $result = $("
").addClass(this.headerRowClass);
+
+ this._eachField(function(field, index) {
+ var $th = this._prepareCell("| ", field, "headercss", this.headerCellClass)
+ .append(this.renderTemplate(field.headerTemplate, field))
+ .appendTo($result);
+
+ if(this.sorting && field.sorting) {
+ $th.addClass(this.sortableClass)
+ .on("click", $.proxy(function() {
+ this.sort(index);
+ }, this));
+ }
+ });
+
+ return $result;
+ },
+
+ _prepareCell: function(cell, field, cssprop, cellClass) {
+ return $(cell).css("width", field.width)
+ .addClass(cellClass || this.cellClass)
+ .addClass((cssprop && field[cssprop]) || field.css)
+ .addClass(field.align ? ("jsgrid-align-" + field.align) : "");
+ },
+
+ _createFilterRow: function() {
+ if($.isFunction(this.filterRowRenderer))
+ return $(this.renderTemplate(this.filterRowRenderer, this));
+
+ var $result = $(" |
").addClass(this.filterRowClass);
+
+ this._eachField(function(field) {
+ this._prepareCell("| ", field, "filtercss")
+ .append(this.renderTemplate(field.filterTemplate, field))
+ .appendTo($result);
+ });
+
+ return $result;
+ },
+
+ _createInsertRow: function() {
+ if($.isFunction(this.insertRowRenderer))
+ return $(this.renderTemplate(this.insertRowRenderer, this));
+
+ var $result = $(" |
").addClass(this.insertRowClass);
+
+ this._eachField(function(field) {
+ this._prepareCell("| ", field, "insertcss")
+ .append(this.renderTemplate(field.insertTemplate, field))
+ .appendTo($result);
+ });
+
+ return $result;
+ },
+
+ _callEventHandler: function(handler, eventParams) {
+ handler.call(this, $.extend(eventParams, {
+ grid: this
+ }));
+
+ return eventParams;
+ },
+
+ reset: function() {
+ this._resetSorting();
+ this._resetPager();
+ return this._loadStrategy.reset();
+ },
+
+ _resetPager: function() {
+ this._firstDisplayingPage = 1;
+ this._setPage(1);
+ },
+
+ _resetSorting: function() {
+ this._sortField = null;
+ this._sortOrder = SORT_ORDER_ASC;
+ this._clearSortingCss();
+ },
+
+ refresh: function() {
+ this._callEventHandler(this.onRefreshing);
+
+ this.cancelEdit();
+
+ this._refreshHeading();
+ this._refreshFiltering();
+ this._refreshInserting();
+ this._refreshContent();
+ this._refreshPager();
+ this._refreshSize();
+
+ this._callEventHandler(this.onRefreshed);
+ },
+
+ _refreshHeading: function() {
+ this._headerRow.toggle(this.heading);
+ },
+
+ _refreshFiltering: function() {
+ this._filterRow.toggle(this.filtering);
+ },
+
+ _refreshInserting: function() {
+ this._insertRow.toggle(this.inserting);
+ },
+
+ _refreshContent: function() {
+ var $content = this._content;
+ $content.empty();
+
+ if(!this.data.length) {
+ $content.append(this._createNoDataRow());
+ return this;
+ }
+
+ var indexFrom = this._loadStrategy.firstDisplayIndex();
+ var indexTo = this._loadStrategy.lastDisplayIndex();
+
+ for(var itemIndex = indexFrom; itemIndex < indexTo; itemIndex++) {
+ var item = this.data[itemIndex];
+ $content.append(this._createRow(item, itemIndex));
+ }
+ },
+
+ _createNoDataRow: function() {
+ var amountOfFields = 0;
+ this._eachField(function() {
+ amountOfFields++;
+ });
+
+ return $(" |
").addClass(this.noDataRowClass)
+ .append($("| ").addClass(this.cellClass).attr("colspan", amountOfFields)
+ .append(this.renderTemplate(this.noDataContent, this)));
+ },
+
+ _createRow: function(item, itemIndex) {
+ var $result;
+
+ if($.isFunction(this.rowRenderer)) {
+ $result = this.renderTemplate(this.rowRenderer, this, { item: item, itemIndex: itemIndex });
+ } else {
+ $result = $(" |
");
+ this._renderCells($result, item);
+ }
+
+ $result.addClass(this._getRowClasses(item, itemIndex))
+ .data(JSGRID_ROW_DATA_KEY, item)
+ .on("click", $.proxy(function(e) {
+ this.rowClick({
+ item: item,
+ itemIndex: itemIndex,
+ event: e
+ });
+ }, this))
+ .on("dblclick", $.proxy(function(e) {
+ this.rowDoubleClick({
+ item: item,
+ itemIndex: itemIndex,
+ event: e
+ });
+ }, this));
+
+ if(this.selecting) {
+ this._attachRowHover($result);
+ }
+
+ return $result;
+ },
+
+ _getRowClasses: function(item, itemIndex) {
+ var classes = [];
+ classes.push(((itemIndex + 1) % 2) ? this.oddRowClass : this.evenRowClass);
+ classes.push(getOrApply(this.rowClass, this, item, itemIndex));
+ return classes.join(" ");
+ },
+
+ _attachRowHover: function($row) {
+ var selectedRowClass = this.selectedRowClass;
+ $row.hover(function() {
+ $(this).addClass(selectedRowClass);
+ },
+ function() {
+ $(this).removeClass(selectedRowClass);
+ }
+ );
+ },
+
+ _renderCells: function($row, item) {
+ this._eachField(function(field) {
+ $row.append(this._createCell(item, field));
+ });
+ return this;
+ },
+
+ _createCell: function(item, field) {
+ var $result;
+ var fieldValue = this._getItemFieldValue(item, field);
+
+ var args = { value: fieldValue, item : item };
+ if($.isFunction(field.cellRenderer)) {
+ $result = this.renderTemplate(field.cellRenderer, field, args);
+ } else {
+ $result = $("").append(this.renderTemplate(field.itemTemplate || fieldValue, field, args));
+ }
+
+ return this._prepareCell($result, field);
+ },
+
+ _getItemFieldValue: function(item, field) {
+ var props = field.name.split('.');
+ var result = item[props.shift()];
+
+ while(result && props.length) {
+ result = result[props.shift()];
+ }
+
+ return result;
+ },
+
+ _setItemFieldValue: function(item, field, value) {
+ var props = field.name.split('.');
+ var current = item;
+ var prop = props[0];
+
+ while(current && props.length) {
+ item = current;
+ prop = props.shift();
+ current = item[prop];
+ }
+
+ if(!current) {
+ while(props.length) {
+ item = item[prop] = {};
+ prop = props.shift();
+ }
+ }
+
+ item[prop] = value;
+ },
+
+ sort: function(field, order) {
+ if($.isPlainObject(field)) {
+ order = field.order;
+ field = field.field;
+ }
+
+ this._clearSortingCss();
+ this._setSortingParams(field, order);
+ this._setSortingCss();
+ return this._loadStrategy.sort();
+ },
+
+ _clearSortingCss: function() {
+ this._headerRow.find("th")
+ .removeClass(this.sortAscClass)
+ .removeClass(this.sortDescClass);
+ },
+
+ _setSortingParams: function(field, order) {
+ field = this._normalizeField(field);
+ order = order || ((this._sortField === field) ? this._reversedSortOrder(this._sortOrder) : SORT_ORDER_ASC);
+
+ this._sortField = field;
+ this._sortOrder = order;
+ },
+
+ _normalizeField: function(field) {
+ if($.isNumeric(field)) {
+ return this.fields[field];
+ }
+
+ if(typeof field === "string") {
+ return $.grep(this.fields, function(f) {
+ return f.name === field;
+ })[0];
+ }
+
+ return field;
+ },
+
+ _reversedSortOrder: function(order) {
+ return (order === SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC);
+ },
+
+ _setSortingCss: function() {
+ var fieldIndex = this._visibleFieldIndex(this._sortField);
+
+ this._headerRow.find("th").eq(fieldIndex)
+ .addClass(this._sortOrder === SORT_ORDER_ASC ? this.sortAscClass : this.sortDescClass);
+ },
+
+ _visibleFieldIndex: function(field) {
+ return $.inArray(field, $.grep(this.fields, function(f) { return f.visible; }));
+ },
+
+ _sortData: function() {
+ var sortFactor = this._sortFactor(),
+ sortField = this._sortField;
+
+ if(sortField) {
+ this.data.sort(function(item1, item2) {
+ return sortFactor * sortField.sortingFunc(item1[sortField.name], item2[sortField.name]);
+ });
+ }
+ },
+
+ _sortFactor: function() {
+ return this._sortOrder === SORT_ORDER_ASC ? 1 : -1;
+ },
+
+ _itemsCount: function() {
+ return this._loadStrategy.itemsCount();
+ },
+
+ _pagesCount: function() {
+ var itemsCount = this._itemsCount(),
+ pageSize = this.pageSize;
+ return Math.floor(itemsCount / pageSize) + (itemsCount % pageSize ? 1 : 0);
+ },
+
+ _refreshPager: function() {
+ var $pagerContainer = this._pagerContainer;
+ $pagerContainer.empty();
+
+ if(this.paging) {
+ $pagerContainer.append(this._createPager());
+ }
+
+ var showPager = this.paging && this._pagesCount() > 1;
+ $pagerContainer.toggle(showPager);
+ },
+
+ _createPager: function() {
+ var $result;
+
+ if($.isFunction(this.pagerRenderer)) {
+ $result = $(this.pagerRenderer({
+ pageIndex: this.pageIndex,
+ pageCount: this._pagesCount()
+ }));
+ } else {
+ $result = $("").append(this._createPagerByFormat());
+ }
+
+ $result.addClass(this.pagerClass);
+
+ return $result;
+ },
+
+ _createPagerByFormat: function() {
+ var pageIndex = this.pageIndex,
+ pageCount = this._pagesCount(),
+ itemCount = this._itemsCount(),
+ pagerParts = this.pagerFormat.split(" ");
+
+ return $.map(pagerParts, $.proxy(function(pagerPart) {
+ var result = pagerPart;
+
+ if(pagerPart === PAGES_PLACEHOLDER) {
+ result = this._createPages();
+ } else if(pagerPart === FIRST_PAGE_PLACEHOLDER) {
+ result = this._createPagerNavButton(this.pageFirstText, 1, pageIndex > 1);
+ } else if(pagerPart === PREV_PAGE_PLACEHOLDER) {
+ result = this._createPagerNavButton(this.pagePrevText, pageIndex - 1, pageIndex > 1);
+ } else if(pagerPart === NEXT_PAGE_PLACEHOLDER) {
+ result = this._createPagerNavButton(this.pageNextText, pageIndex + 1, pageIndex < pageCount);
+ } else if(pagerPart === LAST_PAGE_PLACEHOLDER) {
+ result = this._createPagerNavButton(this.pageLastText, pageCount, pageIndex < pageCount);
+ } else if(pagerPart === PAGE_INDEX_PLACEHOLDER) {
+ result = pageIndex;
+ } else if(pagerPart === PAGE_COUNT_PLACEHOLDER) {
+ result = pageCount;
+ } else if(pagerPart === ITEM_COUNT_PLACEHOLDER) {
+ result = itemCount;
+ }
+
+ return $.isArray(result) ? result.concat([" "]) : [result, " "];
+ }, this));
+ },
+
+ _createPages: function() {
+ var pageCount = this._pagesCount(),
+ pageButtonCount = this.pageButtonCount,
+ firstDisplayingPage = this._firstDisplayingPage,
+ pages = [];
+
+ if(firstDisplayingPage > 1) {
+ pages.push(this._createPagerPageNavButton(this.pageNavigatorPrevText, this.showPrevPages));
+ }
+
+ for(var i = 0, pageNumber = firstDisplayingPage; i < pageButtonCount && pageNumber <= pageCount; i++, pageNumber++) {
+ pages.push(pageNumber === this.pageIndex
+ ? this._createPagerCurrentPage()
+ : this._createPagerPage(pageNumber));
+ }
+
+ if((firstDisplayingPage + pageButtonCount - 1) < pageCount) {
+ pages.push(this._createPagerPageNavButton(this.pageNavigatorNextText, this.showNextPages));
+ }
+
+ return pages;
+ },
+
+ _createPagerNavButton: function(text, pageIndex, isActive) {
+ return this._createPagerButton(text, this.pagerNavButtonClass + (isActive ? "" : " " + this.pagerNavButtonInactiveClass),
+ isActive ? function() { this.openPage(pageIndex); } : $.noop);
+ },
+
+ _createPagerPageNavButton: function(text, handler) {
+ return this._createPagerButton(text, this.pagerNavButtonClass, handler);
+ },
+
+ _createPagerPage: function(pageIndex) {
+ return this._createPagerButton(pageIndex, this.pageClass, function() {
+ this.openPage(pageIndex);
+ });
+ },
+
+ _createPagerButton: function(text, css, handler) {
+ var $link = $(" ").attr("href", EMPTY_HREF)
+ .html(text)
+ .on("click", $.proxy(handler, this));
+
+ return $("").addClass(css).append($link);
+ },
+
+ _createPagerCurrentPage: function() {
+ return $("")
+ .addClass(this.pageClass)
+ .addClass(this.currentPageClass)
+ .text(this.pageIndex);
+ },
+
+ _refreshSize: function() {
+ this._refreshHeight();
+ this._refreshWidth();
+ },
+
+ _refreshWidth: function() {
+ var width = (this.width === "auto") ? this._getAutoWidth() : this.width;
+
+ this._container.width(width);
+ },
+
+ _getAutoWidth: function() {
+ var $headerGrid = this._headerGrid,
+ $header = this._header;
+
+ $headerGrid.width("auto");
+
+ var contentWidth = $headerGrid.outerWidth();
+ var borderWidth = $header.outerWidth() - $header.innerWidth();
+
+ $headerGrid.width("");
+
+ return contentWidth + borderWidth;
+ },
+
+ _scrollBarWidth: (function() {
+ var result;
+
+ return function() {
+ if(result === undefined) {
+ var $ghostContainer = $("");
+ var $ghostContent = $("");
+ $ghostContainer.append($ghostContent).appendTo("body");
+ var width = $ghostContent.innerWidth();
+ $ghostContainer.css("overflow-y", "auto");
+ var widthExcludingScrollBar = $ghostContent.innerWidth();
+ $ghostContainer.remove();
+ result = width - widthExcludingScrollBar;
+ }
+ return result;
+ };
+ })(),
+
+ _refreshHeight: function() {
+ var container = this._container,
+ pagerContainer = this._pagerContainer,
+ height = this.height,
+ nonBodyHeight;
+
+ container.height(height);
+
+ if(height !== "auto") {
+ height = container.height();
+
+ nonBodyHeight = this._header.outerHeight(true);
+ if(pagerContainer.parents(container).length) {
+ nonBodyHeight += pagerContainer.outerHeight(true);
+ }
+
+ this._body.outerHeight(height - nonBodyHeight);
+ }
+ },
+
+ showPrevPages: function() {
+ var firstDisplayingPage = this._firstDisplayingPage,
+ pageButtonCount = this.pageButtonCount;
+
+ this._firstDisplayingPage = (firstDisplayingPage > pageButtonCount) ? firstDisplayingPage - pageButtonCount : 1;
+
+ this._refreshPager();
+ },
+
+ showNextPages: function() {
+ var firstDisplayingPage = this._firstDisplayingPage,
+ pageButtonCount = this.pageButtonCount,
+ pageCount = this._pagesCount();
+
+ this._firstDisplayingPage = (firstDisplayingPage + 2 * pageButtonCount > pageCount)
+ ? pageCount - pageButtonCount + 1
+ : firstDisplayingPage + pageButtonCount;
+
+ this._refreshPager();
+ },
+
+ openPage: function(pageIndex) {
+ if(pageIndex < 1 || pageIndex > this._pagesCount())
+ return;
+
+ this._setPage(pageIndex);
+ this._loadStrategy.openPage(pageIndex);
+ },
+
+ _setPage: function(pageIndex) {
+ var firstDisplayingPage = this._firstDisplayingPage,
+ pageButtonCount = this.pageButtonCount;
+
+ this.pageIndex = pageIndex;
+
+ if(pageIndex < firstDisplayingPage) {
+ this._firstDisplayingPage = pageIndex;
+ }
+
+ if(pageIndex > firstDisplayingPage + pageButtonCount - 1) {
+ this._firstDisplayingPage = pageIndex - pageButtonCount + 1;
+ }
+
+ this._callEventHandler(this.onPageChanged, {
+ pageIndex: pageIndex
+ });
+ },
+
+ _controllerCall: function(method, param, isCanceled, doneCallback) {
+ if(isCanceled)
+ return $.Deferred().reject().promise();
+
+ this._showLoading();
+
+ var controller = this._controller;
+ if(!controller || !controller[method]) {
+ throw Error("controller has no method '" + method + "'");
+ }
+
+ return normalizePromise(controller[method](param))
+ .done($.proxy(doneCallback, this))
+ .fail($.proxy(this._errorHandler, this))
+ .always($.proxy(this._hideLoading, this));
+ },
+
+ _errorHandler: function() {
+ this._callEventHandler(this.onError, {
+ args: $.makeArray(arguments)
+ });
+ },
+
+ _showLoading: function() {
+ if(!this.loadIndication)
+ return;
+
+ clearTimeout(this._loadingTimer);
+
+ this._loadingTimer = setTimeout($.proxy(function() {
+ this._loadIndicator.show();
+ }, this), this.loadIndicationDelay);
+ },
+
+ _hideLoading: function() {
+ if(!this.loadIndication)
+ return;
+
+ clearTimeout(this._loadingTimer);
+ this._loadIndicator.hide();
+ },
+
+ search: function(filter) {
+ this._resetSorting();
+ this._resetPager();
+ return this.loadData(filter);
+ },
+
+ loadData: function(filter) {
+ filter = filter || (this.filtering ? this.getFilter() : {});
+
+ $.extend(filter, this._loadStrategy.loadParams(), this._sortingParams());
+
+ var args = this._callEventHandler(this.onDataLoading, {
+ filter: filter
+ });
+
+ return this._controllerCall("loadData", filter, args.cancel, function(loadedData) {
+ if(!loadedData)
+ return;
+
+ this._loadStrategy.finishLoad(loadedData);
+
+ this._callEventHandler(this.onDataLoaded, {
+ data: loadedData
+ });
+ });
+ },
+
+ getFilter: function() {
+ var result = {};
+ this._eachField(function(field) {
+ if(field.filtering) {
+ this._setItemFieldValue(result, field, field.filterValue());
+ }
+ });
+ return result;
+ },
+
+ _sortingParams: function() {
+ if(this.sorting && this._sortField) {
+ return {
+ sortField: this._sortField.name,
+ sortOrder: this._sortOrder
+ };
+ }
+ return {};
+ },
+
+ getSorting: function() {
+ var sortingParams = this._sortingParams();
+ return {
+ field: sortingParams.sortField,
+ order: sortingParams.sortOrder
+ };
+ },
+
+ clearFilter: function() {
+ var $filterRow = this._createFilterRow();
+ this._filterRow.replaceWith($filterRow);
+ this._filterRow = $filterRow;
+ return this.search();
+ },
+
+ insertItem: function(item) {
+ var insertingItem = item || this._getValidatedInsertItem();
+
+ if(!insertingItem)
+ return $.Deferred().reject().promise();
+
+ var args = this._callEventHandler(this.onItemInserting, {
+ item: insertingItem
+ });
+
+ return this._controllerCall("insertItem", insertingItem, args.cancel, function(insertedItem) {
+ insertedItem = insertedItem || insertingItem;
+ this._loadStrategy.finishInsert(insertedItem);
+
+ this._callEventHandler(this.onItemInserted, {
+ item: insertedItem
+ });
+ });
+ },
+
+ _getValidatedInsertItem: function() {
+ var item = this._getInsertItem();
+ return this._validateItem(item, this._insertRow) ? item : null;
+ },
+
+ _getInsertItem: function() {
+ var result = {};
+ this._eachField(function(field) {
+ if(field.inserting) {
+ this._setItemFieldValue(result, field, field.insertValue());
+ }
+ });
+ return result;
+ },
+
+ _validateItem: function(item, $row) {
+ var validationErrors = [];
+
+ var args = {
+ item: item,
+ itemIndex: this._rowIndex($row),
+ row: $row
+ };
+
+ this._eachField(function(field) {
+ if(!field.validate ||
+ ($row === this._insertRow && !field.inserting) ||
+ ($row === this._getEditRow() && !field.editing))
+ return;
+
+ var fieldValue = this._getItemFieldValue(item, field);
+
+ var errors = this._validation.validate($.extend({
+ value: fieldValue,
+ rules: field.validate
+ }, args));
+
+ this._setCellValidity($row.children().eq(this._visibleFieldIndex(field)), errors);
+
+ if(!errors.length)
+ return;
+
+ validationErrors.push.apply(validationErrors,
+ $.map(errors, function(message) {
+ return { field: field, message: message };
+ }));
+ });
+
+ if(!validationErrors.length)
+ return true;
+
+ var invalidArgs = $.extend({
+ errors: validationErrors
+ }, args);
+ this._callEventHandler(this.onItemInvalid, invalidArgs);
+ this.invalidNotify(invalidArgs);
+
+ return false;
+ },
+
+ _setCellValidity: function($cell, errors) {
+ $cell
+ .toggleClass(this.invalidClass, !!errors.length)
+ .attr("title", errors.join("\n"));
+ },
+
+ clearInsert: function() {
+ var insertRow = this._createInsertRow();
+ this._insertRow.replaceWith(insertRow);
+ this._insertRow = insertRow;
+ this.refresh();
+ },
+
+ editItem: function(item) {
+ var $row = this.rowByItem(item);
+ if($row.length) {
+ this._editRow($row);
+ }
+ },
+
+ rowByItem: function(item) {
+ if(item.jquery || item.nodeType)
+ return $(item);
+
+ return this._content.find("tr").filter(function() {
+ return $.data(this, JSGRID_ROW_DATA_KEY) === item;
+ });
+ },
+
+ _editRow: function($row) {
+ if(!this.editing)
+ return;
+
+ var item = $row.data(JSGRID_ROW_DATA_KEY);
+
+ var args = this._callEventHandler(this.onItemEditing, {
+ row: $row,
+ item: item,
+ itemIndex: this._itemIndex(item)
+ });
+
+ if(args.cancel)
+ return;
+
+ if(this._editingRow) {
+ this.cancelEdit();
+ }
+
+ var $editRow = this._createEditRow(item);
+
+ this._editingRow = $row;
+ $row.hide();
+ $editRow.insertBefore($row);
+ $row.data(JSGRID_EDIT_ROW_DATA_KEY, $editRow);
+ },
+
+ _createEditRow: function(item) {
+ if($.isFunction(this.editRowRenderer)) {
+ return $(this.renderTemplate(this.editRowRenderer, this, { item: item, itemIndex: this._itemIndex(item) }));
+ }
+
+ var $result = $("").addClass(this.editRowClass);
+
+ this._eachField(function(field) {
+ var fieldValue = this._getItemFieldValue(item, field);
+
+ this._prepareCell("", field, "editcss")
+ .append(this.renderTemplate(field.editTemplate || "", field, { value: fieldValue, item: item }))
+ .appendTo($result);
+ });
+
+ return $result;
+ },
+
+ updateItem: function(item, editedItem) {
+ if(arguments.length === 1) {
+ editedItem = item;
+ }
+
+ var $row = item ? this.rowByItem(item) : this._editingRow;
+ editedItem = editedItem || this._getValidatedEditedItem();
+
+ if(!editedItem)
+ return;
+
+ return this._updateRow($row, editedItem);
+ },
+
+ _getValidatedEditedItem: function() {
+ var item = this._getEditedItem();
+ return this._validateItem(item, this._getEditRow()) ? item : null;
+ },
+
+ _updateRow: function($updatingRow, editedItem) {
+ var updatingItem = $updatingRow.data(JSGRID_ROW_DATA_KEY),
+ updatingItemIndex = this._itemIndex(updatingItem),
+ updatedItem = $.extend(true, {}, updatingItem, editedItem);
+
+ var args = this._callEventHandler(this.onItemUpdating, {
+ row: $updatingRow,
+ item: updatedItem,
+ itemIndex: updatingItemIndex,
+ previousItem: updatingItem
+ });
+
+ return this._controllerCall("updateItem", updatedItem, args.cancel, function(loadedUpdatedItem) {
+ var previousItem = $.extend(true, {}, updatingItem);
+ updatedItem = loadedUpdatedItem || $.extend(true, updatingItem, editedItem);
+
+ var $updatedRow = this._finishUpdate($updatingRow, updatedItem, updatingItemIndex);
+
+ this._callEventHandler(this.onItemUpdated, {
+ row: $updatedRow,
+ item: updatedItem,
+ itemIndex: updatingItemIndex,
+ previousItem: previousItem
+ });
+ });
+ },
+
+ _rowIndex: function(row) {
+ return this._content.children().index($(row));
+ },
+
+ _itemIndex: function(item) {
+ return $.inArray(item, this.data);
+ },
+
+ _finishUpdate: function($updatingRow, updatedItem, updatedItemIndex) {
+ this.cancelEdit();
+ this.data[updatedItemIndex] = updatedItem;
+
+ var $updatedRow = this._createRow(updatedItem, updatedItemIndex);
+ $updatingRow.replaceWith($updatedRow);
+ return $updatedRow;
+ },
+
+ _getEditedItem: function() {
+ var result = {};
+ this._eachField(function(field) {
+ if(field.editing) {
+ this._setItemFieldValue(result, field, field.editValue());
+ }
+ });
+ return result;
+ },
+
+ cancelEdit: function() {
+ if(!this._editingRow)
+ return;
+
+ this._getEditRow().remove();
+ this._editingRow.show();
+ this._editingRow = null;
+ },
+
+ _getEditRow: function() {
+ return this._editingRow && this._editingRow.data(JSGRID_EDIT_ROW_DATA_KEY);
+ },
+
+ deleteItem: function(item) {
+ var $row = this.rowByItem(item);
+
+ if(!$row.length)
+ return;
+
+ if(this.confirmDeleting && !window.confirm(getOrApply(this.deleteConfirm, this, $row.data(JSGRID_ROW_DATA_KEY))))
+ return;
+
+ return this._deleteRow($row);
+ },
+
+ _deleteRow: function($row) {
+ var deletingItem = $row.data(JSGRID_ROW_DATA_KEY),
+ deletingItemIndex = this._itemIndex(deletingItem);
+
+ var args = this._callEventHandler(this.onItemDeleting, {
+ row: $row,
+ item: deletingItem,
+ itemIndex: deletingItemIndex
+ });
+
+ return this._controllerCall("deleteItem", deletingItem, args.cancel, function() {
+ this._loadStrategy.finishDelete(deletingItem, deletingItemIndex);
+
+ this._callEventHandler(this.onItemDeleted, {
+ row: $row,
+ item: deletingItem,
+ itemIndex: deletingItemIndex
+ });
+ });
+ }
+ };
+
+ $.fn.jsGrid = function(config) {
+ var args = $.makeArray(arguments),
+ methodArgs = args.slice(1),
+ result = this;
+
+ this.each(function() {
+ var $element = $(this),
+ instance = $element.data(JSGRID_DATA_KEY),
+ methodResult;
+
+ if(instance) {
+ if(typeof config === "string") {
+ methodResult = instance[config].apply(instance, methodArgs);
+ if(methodResult !== undefined && methodResult !== instance) {
+ result = methodResult;
+ return false;
+ }
+ } else {
+ instance._detachWindowResizeCallback();
+ instance._init(config);
+ instance.render();
+ }
+ } else {
+ new Grid($element, config);
+ }
+ });
+
+ return result;
+ };
+
+ var fields = {};
+
+ var setDefaults = function(config) {
+ var componentPrototype;
+
+ if($.isPlainObject(config)) {
+ componentPrototype = Grid.prototype;
+ } else {
+ componentPrototype = fields[config].prototype;
+ config = arguments[1] || {};
+ }
+
+ $.extend(componentPrototype, config);
+ };
+
+ var locales = {};
+
+ var locale = function(lang) {
+ var localeConfig = $.isPlainObject(lang) ? lang : locales[lang];
+
+ if(!localeConfig)
+ throw Error("unknown locale " + lang);
+
+ setLocale(jsGrid, localeConfig);
+ };
+
+ var setLocale = function(obj, localeConfig) {
+ $.each(localeConfig, function(field, value) {
+ if($.isPlainObject(value)) {
+ setLocale(obj[field] || obj[field[0].toUpperCase() + field.slice(1)], value);
+ return;
+ }
+
+ if(obj.hasOwnProperty(field)) {
+ obj[field] = value;
+ } else {
+ obj.prototype[field] = value;
+ }
+ });
+ };
+
+ window.jsGrid = {
+ Grid: Grid,
+ fields: fields,
+ setDefaults: setDefaults,
+ locales: locales,
+ locale: locale,
+ version: '1.5.3'
+ };
+
+}(window, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ function LoadIndicator(config) {
+ this._init(config);
+ }
+
+ LoadIndicator.prototype = {
+
+ container: "body",
+ message: "Loading...",
+ shading: true,
+
+ zIndex: 1000,
+ shaderClass: "jsgrid-load-shader",
+ loadPanelClass: "jsgrid-load-panel",
+
+ _init: function(config) {
+ $.extend(true, this, config);
+
+ this._initContainer();
+ this._initShader();
+ this._initLoadPanel();
+ },
+
+ _initContainer: function() {
+ this._container = $(this.container);
+ },
+
+ _initShader: function() {
+ if(!this.shading)
+ return;
+
+ this._shader = $("").addClass(this.shaderClass)
+ .hide()
+ .css({
+ position: "absolute",
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ zIndex: this.zIndex
+ })
+ .appendTo(this._container);
+ },
+
+ _initLoadPanel: function() {
+ this._loadPanel = $(" ").addClass(this.loadPanelClass)
+ .text(this.message)
+ .hide()
+ .css({
+ position: "absolute",
+ top: "50%",
+ left: "50%",
+ zIndex: this.zIndex
+ })
+ .appendTo(this._container);
+ },
+
+ show: function() {
+ var $loadPanel = this._loadPanel.show();
+
+ var actualWidth = $loadPanel.outerWidth();
+ var actualHeight = $loadPanel.outerHeight();
+
+ $loadPanel.css({
+ marginTop: -actualHeight / 2,
+ marginLeft: -actualWidth / 2
+ });
+
+ this._shader.show();
+ },
+
+ hide: function() {
+ this._loadPanel.hide();
+ this._shader.hide();
+ }
+
+ };
+
+ jsGrid.LoadIndicator = LoadIndicator;
+
+}(jsGrid, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ function DirectLoadingStrategy(grid) {
+ this._grid = grid;
+ }
+
+ DirectLoadingStrategy.prototype = {
+
+ firstDisplayIndex: function() {
+ var grid = this._grid;
+ return grid.option("paging") ? (grid.option("pageIndex") - 1) * grid.option("pageSize") : 0;
+ },
+
+ lastDisplayIndex: function() {
+ var grid = this._grid;
+ var itemsCount = grid.option("data").length;
+
+ return grid.option("paging")
+ ? Math.min(grid.option("pageIndex") * grid.option("pageSize"), itemsCount)
+ : itemsCount;
+ },
+
+ itemsCount: function() {
+ return this._grid.option("data").length;
+ },
+
+ openPage: function(index) {
+ this._grid.refresh();
+ },
+
+ loadParams: function() {
+ return {};
+ },
+
+ sort: function() {
+ this._grid._sortData();
+ this._grid.refresh();
+ return $.Deferred().resolve().promise();
+ },
+
+ reset: function() {
+ this._grid.refresh();
+ return $.Deferred().resolve().promise();
+ },
+
+ finishLoad: function(loadedData) {
+ this._grid.option("data", loadedData);
+ },
+
+ finishInsert: function(insertedItem) {
+ var grid = this._grid;
+ grid.option("data").push(insertedItem);
+ grid.refresh();
+ },
+
+ finishDelete: function(deletedItem, deletedItemIndex) {
+ var grid = this._grid;
+ grid.option("data").splice(deletedItemIndex, 1);
+ grid.reset();
+ }
+ };
+
+
+ function PageLoadingStrategy(grid) {
+ this._grid = grid;
+ this._itemsCount = 0;
+ }
+
+ PageLoadingStrategy.prototype = {
+
+ firstDisplayIndex: function() {
+ return 0;
+ },
+
+ lastDisplayIndex: function() {
+ return this._grid.option("data").length;
+ },
+
+ itemsCount: function() {
+ return this._itemsCount;
+ },
+
+ openPage: function(index) {
+ this._grid.loadData();
+ },
+
+ loadParams: function() {
+ var grid = this._grid;
+ return {
+ pageIndex: grid.option("pageIndex"),
+ pageSize: grid.option("pageSize")
+ };
+ },
+
+ reset: function() {
+ return this._grid.loadData();
+ },
+
+ sort: function() {
+ return this._grid.loadData();
+ },
+
+ finishLoad: function(loadedData) {
+ this._itemsCount = loadedData.itemsCount;
+ this._grid.option("data", loadedData.data);
+ },
+
+ finishInsert: function(insertedItem) {
+ this._grid.search();
+ },
+
+ finishDelete: function(deletedItem, deletedItemIndex) {
+ this._grid.search();
+ }
+ };
+
+ jsGrid.loadStrategies = {
+ DirectLoadingStrategy: DirectLoadingStrategy,
+ PageLoadingStrategy: PageLoadingStrategy
+ };
+
+}(jsGrid, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ var isDefined = function(val) {
+ return typeof(val) !== "undefined" && val !== null;
+ };
+
+ var sortStrategies = {
+ string: function(str1, str2) {
+ if(!isDefined(str1) && !isDefined(str2))
+ return 0;
+
+ if(!isDefined(str1))
+ return -1;
+
+ if(!isDefined(str2))
+ return 1;
+
+ return ("" + str1).localeCompare("" + str2);
+ },
+
+ number: function(n1, n2) {
+ return n1 - n2;
+ },
+
+ date: function(dt1, dt2) {
+ return dt1 - dt2;
+ },
+
+ numberAsString: function(n1, n2) {
+ return parseFloat(n1) - parseFloat(n2);
+ }
+ };
+
+ jsGrid.sortStrategies = sortStrategies;
+
+}(jsGrid, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ function Validation(config) {
+ this._init(config);
+ }
+
+ Validation.prototype = {
+
+ _init: function(config) {
+ $.extend(true, this, config);
+ },
+
+ validate: function(args) {
+ var errors = [];
+
+ $.each(this._normalizeRules(args.rules), function(_, rule) {
+ if(rule.validator(args.value, args.item, rule.param))
+ return;
+
+ var errorMessage = $.isFunction(rule.message) ? rule.message(args.value, args.item) : rule.message;
+ errors.push(errorMessage);
+ });
+
+ return errors;
+ },
+
+ _normalizeRules: function(rules) {
+ if(!$.isArray(rules))
+ rules = [rules];
+
+ return $.map(rules, $.proxy(function(rule) {
+ return this._normalizeRule(rule);
+ }, this));
+ },
+
+ _normalizeRule: function(rule) {
+ if(typeof rule === "string")
+ rule = { validator: rule };
+
+ if($.isFunction(rule))
+ rule = { validator: rule };
+
+ if($.isPlainObject(rule))
+ rule = $.extend({}, rule);
+ else
+ throw Error("wrong validation config specified");
+
+ if($.isFunction(rule.validator))
+ return rule;
+
+ return this._applyNamedValidator(rule, rule.validator);
+ },
+
+ _applyNamedValidator: function(rule, validatorName) {
+ delete rule.validator;
+
+ var validator = validators[validatorName];
+ if(!validator)
+ throw Error("unknown validator \"" + validatorName + "\"");
+
+ if($.isFunction(validator)) {
+ validator = { validator: validator };
+ }
+
+ return $.extend({}, validator, rule);
+ }
+ };
+
+ jsGrid.Validation = Validation;
+
+
+ var validators = {
+ required: {
+ message: "Field is required",
+ validator: function(value) {
+ return value !== undefined && value !== null && value !== "";
+ }
+ },
+
+ rangeLength: {
+ message: "Field value length is out of the defined range",
+ validator: function(value, _, param) {
+ return value.length >= param[0] && value.length <= param[1];
+ }
+ },
+
+ minLength: {
+ message: "Field value is too short",
+ validator: function(value, _, param) {
+ return value.length >= param;
+ }
+ },
+
+ maxLength: {
+ message: "Field value is too long",
+ validator: function(value, _, param) {
+ return value.length <= param;
+ }
+ },
+
+ pattern: {
+ message: "Field value is not matching the defined pattern",
+ validator: function(value, _, param) {
+ if(typeof param === "string") {
+ param = new RegExp("^(?:" + param + ")$");
+ }
+ return param.test(value);
+ }
+ },
+
+ range: {
+ message: "Field value is out of the defined range",
+ validator: function(value, _, param) {
+ return value >= param[0] && value <= param[1];
+ }
+ },
+
+ min: {
+ message: "Field value is too small",
+ validator: function(value, _, param) {
+ return value >= param;
+ }
+ },
+
+ max: {
+ message: "Field value is too large",
+ validator: function(value, _, param) {
+ return value <= param;
+ }
+ }
+ };
+
+ jsGrid.validators = validators;
+
+}(jsGrid, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ function Field(config) {
+ $.extend(true, this, config);
+ this.sortingFunc = this._getSortingFunc();
+ }
+
+ Field.prototype = {
+ name: "",
+ title: null,
+ css: "",
+ align: "",
+ width: 100,
+
+ visible: true,
+ filtering: true,
+ inserting: true,
+ editing: true,
+ sorting: true,
+ sorter: "string", // name of SortStrategy or function to compare elements
+
+ headerTemplate: function() {
+ return (this.title === undefined || this.title === null) ? this.name : this.title;
+ },
+
+ itemTemplate: function(value, item) {
+ return value;
+ },
+
+ filterTemplate: function() {
+ return "";
+ },
+
+ insertTemplate: function() {
+ return "";
+ },
+
+ editTemplate: function(value, item) {
+ this._value = value;
+ return this.itemTemplate(value, item);
+ },
+
+ filterValue: function() {
+ return "";
+ },
+
+ insertValue: function() {
+ return "";
+ },
+
+ editValue: function() {
+ return this._value;
+ },
+
+ _getSortingFunc: function() {
+ var sorter = this.sorter;
+
+ if($.isFunction(sorter)) {
+ return sorter;
+ }
+
+ if(typeof sorter === "string") {
+ return jsGrid.sortStrategies[sorter];
+ }
+
+ throw Error("wrong sorter for the field \"" + this.name + "\"!");
+ }
+ };
+
+ jsGrid.Field = Field;
+
+}(jsGrid, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ var Field = jsGrid.Field;
+
+ function TextField(config) {
+ Field.call(this, config);
+ }
+
+ TextField.prototype = new Field({
+
+ autosearch: true,
+ readOnly: false,
+
+ filterTemplate: function() {
+ if(!this.filtering)
+ return "";
+
+ var grid = this._grid,
+ $result = this.filterControl = this._createTextBox();
+
+ if(this.autosearch) {
+ $result.on("keypress", function(e) {
+ if(e.which === 13) {
+ grid.search();
+ e.preventDefault();
+ }
+ });
+ }
+
+ return $result;
+ },
+
+ insertTemplate: function() {
+ if(!this.inserting)
+ return "";
+
+ return this.insertControl = this._createTextBox();
+ },
+
+ editTemplate: function(value) {
+ if(!this.editing)
+ return this.itemTemplate.apply(this, arguments);
+
+ var $result = this.editControl = this._createTextBox();
+ $result.val(value);
+ return $result;
+ },
+
+ filterValue: function() {
+ return this.filterControl.val();
+ },
+
+ insertValue: function() {
+ return this.insertControl.val();
+ },
+
+ editValue: function() {
+ return this.editControl.val();
+ },
+
+ _createTextBox: function() {
+ return $(" ").attr("type", "text")
+ .prop("readonly", !!this.readOnly);
+ }
+ });
+
+ jsGrid.fields.text = jsGrid.TextField = TextField;
+
+}(jsGrid, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ var TextField = jsGrid.TextField;
+
+ function NumberField(config) {
+ TextField.call(this, config);
+ }
+
+ NumberField.prototype = new TextField({
+
+ sorter: "number",
+ align: "right",
+ readOnly: false,
+
+ filterValue: function() {
+ return this.filterControl.val()
+ ? parseInt(this.filterControl.val() || 0, 10)
+ : undefined;
+ },
+
+ insertValue: function() {
+ return this.insertControl.val()
+ ? parseInt(this.insertControl.val() || 0, 10)
+ : undefined;
+ },
+
+ editValue: function() {
+ return this.editControl.val()
+ ? parseInt(this.editControl.val() || 0, 10)
+ : undefined;
+ },
+
+ _createTextBox: function() {
+ return $(" ").attr("type", "number")
+ .prop("readonly", !!this.readOnly);
+ }
+ });
+
+ jsGrid.fields.number = jsGrid.NumberField = NumberField;
+
+}(jsGrid, jQuery));
+
+(function(jsGrid, $, undefined) {
+
+ var TextField = jsGrid.TextField;
+
+ function TextAreaField(config) {
+ TextField.call(this, config);
+ }
+
+ TextAreaField.prototype = new TextField({
+
+ insertTemplate: function() {
+ if(!this.inserting)
+ return "";
+
+ return this.insertControl = this._createTextArea();
+ },
+
+ editTemplate: function(value) {
+ if(!this.editing)
+ return this.itemTemplate.apply(this, arguments);
+
+ var $result = this.editControl = this._createTextArea();
+ $result.val(value);
+ return $result;
+ },
+
+ _createTextArea: function() {
+ return $(" | |