/**
|
* 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,"<");
|
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();
|
}
|
};
|
}]);
|