/** * Created by Jeong on 2016-10-11. */ 'use strict'; var directiveModule = angular.module('js-input-autocomplete', []); directiveModule.directive('jsInputAutocomplete', ['$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 : "=", customInput : "=", }, templateUrl : "custom_components/js-input-autocomplete/js-input-autocomplete.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, closeOnSelect: false, buttonClasses: 'btn btn-default btn-sm', closeOnDeselect: false, 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); $scope.fn = { getSource : getSource, initOptionSelected : initOptionSelected, selectTarget : selectTarget, toggleDropdown : toggleDropdown, findBeforeFocus : findBeforeFocus, findNextFocus : findNextFocus, getFindObj : getFindObj, clearObject : clearObject, 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 modalSelectModelHeight = ($scope.selectedModel.length * 20) + 30; // row + th컬럼 높이 var offsetTop = $($element).find(".auto-complete-input").offset().top; var multiDiv = newValue.length * 25; var currentHeight = offsetTop + modalSelectModelHeight; // 현재 모달 높이 var divHeight = multiDiv - modalSelectModelHeight; // 검색된 정보의 div 높이 - input 밑 테이블 높이 var requireHeight = offsetTop + divHeight; var addHeight = requireHeight - currentHeight; if (addHeight > 0) { $(".modal-body").height(modalBodyHeight + addHeight); } } } }); $scope.$watch("selectedModel", function (newValue) { if (angular.isDefined(newValue)) { if (newValue.length < 1) { $scope.texts.totalSelected = null; } } if (angular.isDefined($scope.changeEvent)) { $scope.changeEvent(); } }); if($attrs["broadCast"] === "assigneeUpload") { $($element).find(".auto-complete-input").focus(); } $scope.externalEvents.onInitDone(); // 데이터가 없을 때 맞는 문구를 출력하기 위해 사용한다. 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.page = 0; if (!angular.isDefined($scope.customInput) && !$scope.customInput) { $scope.search = ""; } $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 getSource() { // 요소 찾기 전 scope 업데이트 if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') { $scope.$apply(); } $scope.source().then(function (response) { $scope.options = response; $scope.networkSuccess = true; if($attrs["broadCast"] === "assigneeUpload") { $($element).find(".auto-complete-input").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"])) { if($attrs["broadCast"] === "assigneeUpload"){ $rootScope.$broadcast($attrs["broadCast"], {id : id}); }else{ $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(); $scope.fn.getSource(); }); } // 목록 레이어 제어 function toggleDropdown() { $scope.open = true; $($element).unbind("keyup"); $($element).unbind("keydown"); // 옵션 선택 초기화 $scope.fn.initOptionSelected(); // 최초 열릴 때 통신 시작. if ($scope.options.length < 1) { $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) { $scope.open = true; switch (event.keyCode) { case 9 : // 탭키 닫기 $scope.open = false; if (!angular.isDefined($scope.customInput) && !$scope.customInput) { $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 $targetA = $($element).find(".option-selected").find("a"); var id = $targetA.attr("data-id"); if (angular.isDefined(id)) { $scope.fn.selectTarget(id); return false; } break; case 38 : // 위 event.preventDefault(); event.stopPropagation(); var $targetBefore = $scope.fn.findBeforeFocus(); if ($targetBefore != null) { $targetBefore.addClass("option-selected"); $targetBefore.find("a").focus(); } break; case 40 : // 아래 event.preventDefault(); event.stopPropagation(); var $targetNext = $scope.fn.findNextFocus(); $targetNext.addClass("option-selected"); $targetNext.find("a").focus(); break; case 27 : // 팝업 닫기 esc $scope.open = false; $scope.$apply(); event.preventDefault(); event.stopPropagation(); break; default : // 옵션에 active가 들어간 대상이 있을 경우 키보드 입력 제한. var searchTarget = $($element).find(".option-selected"); if (angular.isDefined(searchTarget[0])) { event.preventDefault(); event.stopPropagation(); } } }); } // 이전 요소 찾기 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 (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); } }); 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 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.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; } $scope.fn.closeLayer(); } }; }]);