/** * Created by Jeong on 2016-10-11. */ 'use strict'; var directiveModule = angular.module('js-autocomplete-single', []); directiveModule.directive('jsAutocompleteSingle', ['$filter', '$document', '$compile', '$parse', '$log', '$timeout', '$rootScope', function ($filter, $document, $compile, $parse, $log, $timeout, $rootScope) { return { restrict : 'AE', scope : { selectedModel : '=', extraSettings : '=', events : '=', searchFilter : '=?', translationTexts : '=', customText : "=", changeEvent : "=changeEvent", search : "=", source : '&', page : "=", totalPage : "=", inputDisabled : "=", }, templateUrl : "custom_components/js-autocomplete-single/js-autocomplete-single.html", link : function ($scope, $element, $attrs) { var $dropdownTrigger = $element.children()[0]; var blank_pattern = /^\s+|\s+$/g; // 입력 필드 비활성화 if (!angular.isDefined($scope.inputDisabled)) { $scope.inputDisabled = false; } $scope.options = []; // 전체 목록 $scope.networkSuccess = false; // 통신 완료 후 화면 표시. $scope.externalEvents = { onItemSelect : angular.noop, onItemDeselect : angular.noop, onInitDone : angular.noop, }; $scope.settings = { scrollable : false, scrollableHeight : '300px', displayProp : '', idProp : '', externalIdProp : '', selectionLimit : 0, buttonClasses : 'btn btn-default btn-sm', width : '', widthable : false, imageable : false, imagePathProp : '', type : '', maxlength : 50, autoResize : false }; $scope.texts = { checkAll : "common.checkAll", unCheckAll : "common.unCheckAll", selectionCount : "common.selecte", selectionOf : '/', searchPlaceholder : 'Search...', buttonDefaultText : "common.select", dynamicButtonTextSuffix : "common.select", empty : "common.noData", totalSelected : null, selectYn : false }; $scope.searchFilter = $scope.searchFilter || ''; angular.extend($scope.settings, $scope.extraSettings || []); angular.extend($scope.externalEvents, $scope.events || []); angular.extend($scope.texts, $scope.translationTexts); $log.debug("확인 : " , $scope.texts); $scope.fn = { searchInputField : searchInputField, getSource : getSource, deleteTag : deleteTag, initOptionSelected : initOptionSelected, selectTarget : selectTarget, toggleDropdown : toggleDropdown, findBeforeFocus : findBeforeFocus, findNextFocus : findNextFocus, getFindObj : getFindObj, clearObject : clearObject, getButtonText : getButtonText, getPropertyForObject : getPropertyForObject, setSelectedItem : setSelectedItem, isChecked : isChecked, closeLayer : closeLayer, // 레이어 팝업 닫히는 이벤트 제어 getTranslateKey : getTranslateKey // 데이터가 없을 때 맞는 문구를 출력하기 위해 사용한다. }; // options 내용 변경시 텍스트 초기화 $scope.$watch("options", function (newValue, oldValue) { $scope.texts.totalSelected = null; if (angular.isDefined(newValue)) { angular.forEach(newValue, function (option) { option[$scope.settings.displayProp] = option[$scope.settings.displayProp].replace(//g,">"); }); } // 팝업창에서 검색대상이 표시되면 팝업 사이즈를 동적으로 변경한다. 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, oldValue) { if (angular.isDefined(newValue)) { if (newValue.length < 1) { $scope.texts.totalSelected = null; $(".modal-body").height("auto"); } } if (angular.isDefined($scope.changeEvent)) { $scope.changeEvent(); } }); // owl-auto-focus 어트리뷰트 존재하면 인풋에 포커스 if ("owlAutoFocus" in $attrs) { $($element).find('input').focus(); } $scope.externalEvents.onInitDone(); // 데이터가 없을 때 맞는 문구를 출력하기 위해 사용한다. function getTranslateKey(key) { return $scope.texts[key]; } // 검색된 아이템 클릭시 입력 필드 포커스 주기 function searchInputField() { if ($scope.selectedModel.length > 0) { $($element).find(".input-tag-search-field-readonly").focus(); } } // 서버에서 데이터 조회 function getSource() { $scope.source().then(function (response) { $scope.options = response; $scope.networkSuccess = true; }); } // 선택한 대상 초기화 function deleteTag() { $scope.selectedModel = []; // 브로드 캐스트가 옵션일 경우 실행. if (angular.isDefined($attrs["broadCast"])) { $rootScope.$broadcast($attrs["broadCast"], $scope.selectedModel); } // 요소 찾기 전 scope 업데이트 if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } $timeout(function () { $($element).find(".input-search").focus(); }); } // 현재 선택한 옵션 초기화 function initOptionSelected() { $($element).find(".option-target").each(function () { $(this).removeClass("option-selected"); }); } // 대상을 선택한 경우 실행 function selectTarget(id) { $scope.search = ""; $scope.page = 0; $scope.totalPage = 0; $scope.fn.initOptionSelected(); // 선택 대상 class 초기화 $scope.fn.setSelectedItem(Number(id)); // 브로드 캐스트가 옵션일 경우 실행. if (angular.isDefined($attrs["broadCast"])) { $rootScope.$broadcast($attrs["broadCast"], $scope.selectedModel); } var displaySearchText = $scope.selectedModel[0][$scope.settings.displayProp].split("("); $scope.search = displaySearchText[0].trim(); // 요소 찾기 전 scope 업데이트 if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } $timeout(function () { $($element).find(".input-search").focus(); }); } // 목록 레이어 제어 function toggleDropdown() { // 아이템이 선택되어 있을 경우 목록 레이어는 표시될 수 없다. $scope.open = true; // if ($scope.selectedModel.length > 0) { // $scope.open = false; // $scope.options = []; // } // else { // $scope.open = true; // } // 팝업 창에서 입력 필드에 포커스가 가면 자동 스크롤. if ($scope.settings.autoResize) { $(".modal-body").animate({ scrollTop : $(".modal-body").scrollTop() + 420 }, 500); } $($element).unbind("keyup"); $($element).unbind("keydown"); if ($scope.open) { // 옵션 선택 초기화 $scope.fn.initOptionSelected(); // 최초 열릴 때 통신 시작. if ($scope.options.length == 0) { $scope.fn.getSource(); } $element.bind("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; } // 검색어가 입력되면 페이징 초기화 $scope.page = 0; $scope.totalPage = 0; if ($scope.search.length > 0) { if ($scope.search.replace(blank_pattern, '') == "") { $scope.search = ""; return false; } } if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } $scope.fn.getSource(); }); $($element).keydown(function (event) { switch (event.keyCode) { case 9 : // 탭키 닫기 $scope.open = false; $scope.search = ""; $scope.page = 0; $scope.totalPage = 0; $scope.options = []; $scope.fn.initOptionSelected(); if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } break; case 32 : // 대상 선택 event.preventDefault(); event.stopPropagation(); var target = $(".option-selected").find("a"); var id = target.attr("data-id"); if (angular.isDefined(id)) { $scope.fn.selectTarget(id); return false; } 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; default : // 옵션에 active가 들어간 대상이 있을 경우 키보드 입력 제한. var searchTarget = $(".option-selected"); if (angular.isDefined(searchTarget[0])) { event.preventDefault(); event.stopPropagation(); } } }); } else { // toggle이 닫혔을 때 $($element).keydown(function (event) { // 상단 숫자 키패드 if (47 < event.keyCode && 58 > event.keyCode) { event.preventDefault(); } // 영문 입력 키패드 if (64 < event.keyCode && 91 > event.keyCode) { event.preventDefault(); } // 우측 키패드 if (95 < event.keyCode && 112 > event.keyCode) { event.preventDefault(); } // 특수 기호 & 한글 입력 if (event.keyCode == 186 || event.keyCode == 187 || event.keyCode == 188 || event.keyCode == 189 || event.keyCode == 190 || event.keyCode == 191 || event.keyCode == 192 || event.keyCode == 219 || event.keyCode == 220 || event.keyCode == 221 || event.keyCode == 222 || event.keyCode == 229 || event.keyCode == 32) { event.preventDefault(); } // 삭제 대기 아이템 css 제거 if (event.keyCode == 8 || event.keyCode == 46) { $scope.fn.deleteTag(); // 백스페이스 & delete키 } else if (event.keyCode == 9) { $scope.open = false; if ($scope.selectedModel.length == 0) { $scope.search = ""; } $scope.page = 0; $scope.totalPage = 0; $scope.fn.initOptionSelected(); if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } } }); } } // 이전 대상 선택 function findBeforeFocus() { var searchTarget = $($element).find(".option-selected"); var target = 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 = $(this).prev(); } else { $timeout(function () { $($element).find(".input-search").focus(); }); } } }); } 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 () { 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 () { if ($(this).find("a").attr("data-id") == currentTargetId) { target = $(this); } }); if ($scope.page + 1 <= $scope.totalPage) { $scope.page++; if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); } $scope.source().then(function (response) { angular.forEach(response, function (target) { $scope.options.push(target); }); }); } } } 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 clearObject(object) { for (var prop in object) { delete object[prop]; } } // 레이어 팝업 닫히는 이벤트 제어 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 (typeof target.className.split != 'function') { break; } if (_.contains(target.className.split(' '), 'multiselect-parent') && !parentFound) { if (target === $dropdownTrigger) { parentFound = true; } } target = target.parentElement; } if (!parentFound) { $scope.$apply(function () { $scope.open = false; if ($scope.selectedModel == null || $scope.selectedModel.length == 0) { $scope.search = ""; } $scope.page = 0; $scope.totalPage = 0; $scope.options = []; $scope.networkSuccess = false; // 삭제 대기 아이템 css 제거 $($element).find(".input-tag").removeClass("remove-ready-item"); }); } }); }); } // 팝업 창이 올라왔을 때 body 이벤트 사라지는 현상을 수정하기 재실행. $scope.$on("closeLayer", function (args) { $scope.fn.closeLayer(); }); // 버튼 텍스트를 동적으로 변경한다. 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; 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 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 = []; $scope.selectedModel.push(finalObj); $scope.externalEvents.onItemSelect(finalObj); } } // 체크 여부를 판단한다. function isChecked(id) { return _.findIndex($scope.selectedModel, $scope.fn.getFindObj(id)) !== -1; } // 레이어 팝업 닫히는 이벤트 제어 $scope.fn.closeLayer(); } }; }]);