/** * Created by Jeong on 2016-10-13. */ 'use strict'; var directiveModule = angular.module('js-autocomplete-multi', []); directiveModule.directive('jsAutocompleteMulti', ['$filter', '$document', '$compile', '$parse', '$log', '$timeout', function ($filter, $document, $compile, $parse, $log, $timeout) { return { restrict : 'AE', scope : { selectedModel : '=', extraSettings : '=', events : '=', customText : "=", changeEvent : "=changeEvent", search : "=", source : '&', inputDisabled : "=", translationTexts: '=' }, templateUrl : "custom_components/js-autocomplete-multi/js-autocomplete-multi.html", link : function ($scope, $element, $attrs) { var $dropdownTrigger = $element.children()[0]; var blank_pattern = /^\s+|\s+$/g; $scope.externalEvents = { onItemSelect : angular.noop, onItemDeselect : angular.noop, onDeselectAll : angular.noop, onInitDone : angular.noop }; $scope.settings = { scrollable : false, // 필요 scrollableHeight : '300px', // 필요 displayProp : '', // 필요 idProp : '', // 필요 externalIdProp : '', selectionLimit : 0, // 필요 buttonClasses : 'btn btn-default multi-select-form-control', // 필요 width : '', // 필요 widthable : false, // 필요 imageable : false, // 필요 imagePathProp : '', // 필요 type : '', // 필요 - 사용자일경우 프로필로 표시하기 위해 maxlength : 50, // 검색 필드 길이 제한 autoResize : false, // 필요 }; $scope.texts = { unCheckAll : "common.unCheckAll", buttonDefaultText : "common.select", dynamicButtonTextSuffix : "common.select", totalSelected : null, count : "common.few", // 선택 갯수 문구 empty : "common.noData" // 선택 가능 데이터가 없을 때 문구 }; angular.extend($scope.settings, $scope.extraSettings || []); angular.extend($scope.externalEvents, $scope.events || []); angular.extend($scope.texts, $scope.translationTexts); // 함수 모음 $scope.fn = { initOptionSelected : initOptionSelected, getSource : getSource, clickActionAddItem : clickActionAddItem, clickActionRemovedItem : clickActionRemovedItem, getSelectAbleTargetList : getSelectAbleTargetList, toggleDropdown : toggleDropdown, findBeforeFocus : findBeforeFocus, findNextFocus : findNextFocus, getFindObj : getFindObj, getButtonText : getButtonText, getPropertyForObject : getPropertyForObject, deselectAll : deselectAll, setSelectedItem : setSelectedItem, isChecked : isChecked, closeLayer : closeLayer, getTranslateKey : getTranslateKey // 데이터가 없을 때 맞는 문구를 출력하기 위해 사용한다. }; // 입력 필드 비활성화 if (!angular.isDefined($scope.inputDisabled)) { $scope.inputDisabled = false; } // 데이터가 없을 때 맞는 문구를 출력하기 위해 사용한다. function getTranslateKey(key) { return $scope.texts[key]; } // 레이어 팝업 닫히는 이벤트 제어 function closeLayer() { $(document).ready(function () { $("body").click(function (e) { var target = e.target.parentElement; var parentFound = false; while (angular.isDefined(target) && target !== null && !parentFound) { if (_.contains(target.className.split(' '), 'multiselect-parent') && !parentFound) { if (target === $dropdownTrigger) { parentFound = true; } } target = target.parentElement; } // 유동적으로 변경되는 값이 li를 그릴 경우 부모를 찾지 못하는 오류 수정. if (target == null) { if ($(e.target.parentElement).hasClass("option-selected-li") || $(e.target.parentElement).hasClass("option-selected-ul") || $(e.target.parentElement).hasClass("element-item")) { parentFound = true; } } if (!parentFound) { $scope.$apply(function () { $scope.open = false; $scope.search = ""; $scope.options = []; }); } }); }); } // 팝업 창이 올라왔을 때 body 이벤트 사라지는 현상을 수정하기 재실행. $scope.$on("closeLayer", function (args) { $scope.fn.closeLayer(); }); // 현재 선택한 옵션 초기화 function initOptionSelected() { $($element).find(".option-target").each(function (index) { $(this).removeClass("option-selected"); }); } // 서버에서 데이터 조회 function getSource() { $scope.source().then(function (response) { $scope.options = response; }); } // 마우스로 대상 클릭시 option-selected 클래스 초기화, 선택된 대상 추가 및 입력 필드 포커스 function clickActionAddItem(id) { $scope.fn.initOptionSelected(); $scope.fn.setSelectedItem(id); $timeout(function () { $($element).find(".select-search").focus(); }); } // 마우스로 대상 클릭시 option-selected 클래스 초기화, 선택된 대상 제거 및 입력 필드 포커스 function clickActionRemovedItem(id) { $scope.fn.initOptionSelected(); $scope.fn.setSelectedItem(id); $timeout(function () { $($element).find(".select-search").focus(); }); $scope.fn.getSource(); } // 선택 가능한 항목만 표시 function getSelectAbleTargetList() { var targetList = []; angular.forEach($scope.options, function (option) { var searchCheck = false; for (var count in $scope.selectedModel) { if ($scope.selectedModel[count][$scope.settings.idProp] == option[$scope.settings.idProp]) { searchCheck = true; break; } } if (!searchCheck) { option[$scope.settings.displayProp] = option[$scope.settings.displayProp].replace(//g,">"); targetList.push(option); } }); return targetList; } // 레이어 팝업을 제어 function toggleDropdown($event) { $($element).unbind("keydown"); $($element).unbind("keyup"); $scope.options = []; if (!$scope.open && (($event.type == "keydown" && $event.keyCode == 40) || ($event.type == "click"))) { $scope.open = !$scope.open; $scope.search = ""; // 팝업 창에서 입력 필드에 포커스가 가면 자동 스크롤. if ($scope.settings.autoResize) { $(".modal-body").animate({ scrollTop : $(".modal-body").scrollTop() + 420 }, 500); } if ($scope.open) { // 옵션 선택 초기화 $scope.fn.initOptionSelected(); $timeout(function () { $($element).find(".select-search").focus(); }); if ($event.type != "click") { event.preventDefault(); event.stopPropagation(); } // 최초 열릴 때 통신 시작. if ($scope.options.length == 0) { $scope.fn.getSource(); } $($element).keydown(function (event) { switch (event.keyCode) { case 13 : event.preventDefault(); event.stopPropagation(); $scope.open = false; $scope.fn.initOptionSelected(); $timeout(function () { $($element).find(".select-display-btn").focus(); }); break; case 9 : // 탭키 닫기 $scope.open = false; $scope.search = ""; $scope.fn.initOptionSelected(); if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } break; case 32 : // 대상 선택 var $target = $($element).find(".option-selected").find("a"); var id = $target.attr("data-id"); var $targetParentUl = $($element).find(".option-selected").parent(); // 현재 포커스 위치가 선택됨 인지 선택 가능인지 확인 // 각 요소를 검색하여 이전/이후 갈수 있는지 확인 // 둘다 갈수 없다면 검색 필드로 포커스 이동 // 선택 요소 찾을 경우 if (angular.isDefined(id)) { // 선택 class 전체 제거 $scope.fn.initOptionSelected(); // 선택된 요소일 경우 if ($($targetParentUl).hasClass("multi-select-option-ul-max5")) { var elementSize = $($element).find(".multi-select-option-ul-max5").find(".option-target").length; // 요소가 1개보다 작다면 포커스는 검색 필드로 이동. if (elementSize < 2) { $timeout(function () { $($element).find(".select-search").focus(); }); } else { var $bofore = null; var $afeter = null; // 요소의 위치 앞, 뒤를 찾는다. $($element).find(".multi-select-option-ul-max5").find(".option-target").each(function (index) { // 선택된 대상을 찾으면 if ($(this).find("a").attr("data-id") == id) { $bofore = $(this).prev(); $afeter = $(this).next(); } }); // 앞 뒤가 존재 할 경우 앞에 지정. if (angular.isDefined($bofore[0]) && angular.isDefined($afeter[0])) { $($bofore).addClass("option-selected"); $($bofore).find("a").focus(); } // 앞이 존재 하지 않을 경우 뒤로 지정 if (!angular.isDefined($bofore[0]) && angular.isDefined($afeter[0])) { $($afeter).addClass("option-selected"); $($afeter).find("a").focus(); } // 뒤가 존재 하지 않을 경우 앞에 지정 if (angular.isDefined($bofore[0]) && !angular.isDefined($afeter[0])) { $($bofore).addClass("option-selected"); $($bofore).find("a").focus(); } // 둘다 존재 하지 않는 경우.. 없어야 하지만 만약을 위해. if (!angular.isDefined($bofore[0]) && !angular.isDefined($afeter[0])) { $timeout(function () { $($element).find(".select-search").focus(); }); } } $scope.fn.setSelectedItem(Number(id)); $scope.fn.getSource(); } else { // 선택하지 않은 요소 일경우 var elementSize = $($element).find(".multi-select-option-ul").find(".option-target").length; // 요소가 1개보다 작다면 포커스는 검색 필드로 이동. if (elementSize < 2) { $timeout(function () { $($element).find(".select-search").focus(); }); } else { var $bofore = null; var $afeter = null; // 요소의 위치 앞, 뒤를 찾는다. $($element).find(".multi-select-option-ul").find(".option-target").each(function (index) { // 선택된 대상을 찾으면 if ($(this).find("a").attr("data-id") == id) { $bofore = $(this).prev(); $afeter = $(this).next(); } }); // 앞 뒤가 존재 할 경우 앞에 지정. if (angular.isDefined($bofore[0]) && angular.isDefined($afeter[0])) { $($bofore).addClass("option-selected"); $($bofore).find("a").focus(); } // 앞이 존재 하지 않을 경우 뒤로 지정 if (!angular.isDefined($bofore[0]) && angular.isDefined($afeter[0])) { $($afeter).addClass("option-selected"); $($afeter).find("a").focus(); } // 뒤가 존재 하지 않을 경우 앞에 지정 if (angular.isDefined($bofore[0]) && !angular.isDefined($afeter[0])) { $($bofore).addClass("option-selected"); $($bofore).find("a").focus(); } // 둘다 존재 하지 않는 경우.. 없어야 하지만 만약을 위해. if (!angular.isDefined($bofore[0]) && !angular.isDefined($afeter[0])) { $timeout(function () { $($element).find(".select-search").focus(); }); } } $scope.fn.setSelectedItem(Number(id)); } event.preventDefault(); event.stopPropagation(); } break; case 38 : // 위 event.preventDefault(); event.stopPropagation(); var target = $scope.fn.findBeforeFocus(); if (target != null) { target.addClass("option-selected"); target.find("a").focus(); } break; case 40 : // 아래 event.preventDefault(); event.stopPropagation(); var target = $scope.fn.findNextFocus(); target.addClass("option-selected"); target.find("a").focus(); break; case 27 : // 팝업 닫기 esc $scope.open = false; $scope.$apply(); event.preventDefault(); event.stopPropagation(); break; } }); $($element).keyup(function (e) { if (e.keyCode == 40 || e.keyCode == 38 || e.keyCode == 13 || e.keyCode == 9) { return false; } // 옵션에 active 가 들어간 대상이 있을 경우 키보드 입력 제한. var searchTarget = $($element).find(".option-selected"); if (angular.isDefined(searchTarget[0])) { event.preventDefault(); event.stopPropagation(); return false; } if ($scope.search.length > 0) { if ($scope.search.replace(blank_pattern, '') == "") { $scope.search = ""; return false; } } // 검색 들어가면 기존에 선택한 option-selected 초기화 $scope.fn.initOptionSelected(); if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } $scope.fn.getSource(); }); } } else { if ($event.type == "click" && $scope.open) { $scope.open = false; } } } // 이전 대상 포커스 function findBeforeFocus() { var $searchTarget = $($element).find(".option-selected"); var target = null; var $beforeTarget = null; if (angular.isDefined($searchTarget[0])) { var currentTargetId = $($searchTarget).find("a").attr("data-id"); $($element).find(".option-target").each(function (index) { $(this).removeClass("option-selected"); if ($(this).find("a").attr("data-id") == currentTargetId) { if (index > 0) { target = $beforeTarget; } else { $timeout(function () { $($element).find(".select-search").focus(); }); } } // 이전 대상을 미리 저장해놓는다. $beforeTarget = $(this); }); } return target; } // 다음 대상 포커스 function findNextFocus() { var $searchTarget = $($element).find(".option-selected"); var target = null; // 선택할 수 있는 대상이 존재할 경우 if (angular.isDefined($searchTarget[0])) { var currentTargetId = $($searchTarget).find("a").attr("data-id"); var nextCheck = false; $($element).find(".option-target").each(function (index) { if (nextCheck) { target = $(this); nextCheck = false; } $(this).removeClass("option-selected"); if ($(this).find("a").attr("data-id") == currentTargetId) { nextCheck = true; } }); if (target == null) { $($element).find(".option-target").each(function (index) { if ($(this).find("a").attr("data-id") == currentTargetId) { target = $(this); } }); } } else { target = $($element).find(".option-target").eq(0); $($element).find(".multi-select-option-ul").scrollTop(); } return target; } // id로 해당 객체를 찾는다. function getFindObj(id) { var findObj = {}; if ($scope.settings.externalIdProp === '') { findObj[$scope.settings.idProp] = id; } else { findObj[$scope.settings.externalIdProp] = id; } return findObj; } // 화면에 표시되는 버튼 텍스트를 동적으로 변경한다. function getButtonText() { if (!angular.isDefined($scope.selectedModel)) { $scope.selectedModel = []; } if (($scope.selectedModel.length > 0 || (angular.isObject($scope.selectedModel) && _.keys($scope.selectedModel).length > 0))) { var totalSelected = angular.isDefined($scope.selectedModel) ? $scope.selectedModel.length : 0; if (totalSelected === 0) { return $scope.customText == undefined ? $scope.texts.buttonDefaultText : $scope.customText; } else { $scope.texts.totalSelected = totalSelected; return $scope.texts.dynamicButtonTextSuffix; } } else { return $scope.customText == undefined ? $scope.texts.buttonDefaultText : $scope.customText; } } // 객체에서 해당 프로퍼티를 추출한다. function getPropertyForObject(object, property) { if (angular.isDefined(object) && object.hasOwnProperty(property)) { return object[property]; } return ''; } // 전체 대상의 선택을 해제한다. function deselectAll(sendEvent) { // 옵션 포커스 초기화 $scope.fn.initOptionSelected(); $scope.texts.totalSelected = null; sendEvent = sendEvent || true; if (sendEvent) { $scope.externalEvents.onDeselectAll(); } $scope.selectedModel = []; $timeout(function () { $($element).find(".select-search").focus(); $scope.fn.getSource(); // IE 전체 초기화 버튼 클릭시 ul 영역 늘어나는 현상 수정 $($element).find(".multi-select-option-ul-max5").height("100%"); $($element).find(".multi-select-option-ul-max5").height("auto"); }); } // 대상을 선택한다. function setSelectedItem(id, dontRemove) { var findObj = $scope.fn.getFindObj(id); var finalObj = null; if ($scope.settings.externalIdProp === '') { finalObj = _.find($scope.options, findObj); } else { finalObj = findObj; } dontRemove = dontRemove || false; var exists = _.findIndex($scope.selectedModel, findObj) !== -1; if (!dontRemove && exists) { $scope.selectedModel.splice(_.findIndex($scope.selectedModel, findObj), 1); $scope.externalEvents.onItemDeselect(findObj); $scope.texts.totalSelected = null; } else if (!exists && ($scope.settings.selectionLimit === 0 || $scope.selectedModel.length < $scope.settings.selectionLimit)) { $scope.selectedModel.push(finalObj); $scope.externalEvents.onItemSelect(finalObj); } if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } } // 체크 여부를 판단한다. function isChecked(id) { return _.findIndex($scope.selectedModel, $scope.fn.getFindObj(id)) !== -1; } // options 내용 변경시 텍스트 초기화 $scope.$watch("options", function (newValue, oldValue) { $scope.texts.totalSelected = null; if (!angular.isDefined(newValue)) { newValue = []; } // 팝업창에서 검색대상이 표시되면 팝업 사이즈를 동적으로 변경한다. if ($scope.settings.autoResize) { $(".modal-body").height("auto"); // 팝업 창에서 동적으로 팝업 창 높이를 변경해준다. if (newValue.length > 0) { var modalBodyHeight = $(".modal-body").height(); var multiDiv = $($element).find(".multi-select-option-ul").height(); $(".modal-body").height(modalBodyHeight + multiDiv); } } }); // 선택한 대상 정보가 변경되는지 감지한다. $scope.$watch("selectedModel", function (newValue) { if (angular.isDefined(newValue)) { if (newValue.length < 1) { $scope.texts.totalSelected = null; } } if (angular.isDefined($scope.changeEvent)) { $scope.changeEvent(); } }); $scope.externalEvents.onInitDone(); $scope.fn.closeLayer(); } }; }]);