Kimsora✨
article thumbnail
Published 2023. 3. 7. 11:45
드래그앤 드롭(js) Javascript
320x100
반응형

클래스형은 너무 어렵다..  그냥 어떻게 돌아가는지만 구조만 알아두자..

 

자바스크립트 강의

이벤트 핸들러 함수 & “this”

  • JavaScript에서 함수를 호출할 때, 함수 내에서 this 키워드가 가리키는 대상은 함수를 호출하는 방법에 따라 달라진다
  • addEventListener() 메서드를 사용하여 이벤트 핸들러 함수를 등록한 경우 ⇒이벤트 핸들러 함수 내에서 this는 해당 요소를 가리킨다
const myButton = document.querySelector('#my-button');

myButton.addEventListener('click', function() {
  console.log('버튼이 클릭되었습니다.');
  console.log(this); // myButton 요소 출력
});

 

 

  • bind()를 사용하여 함수 내에서 this를 특정 값으로 고정 ⇒bind()는 첫 번째 매개변수로 함수 내에서 this 키워드가 가리킬 대상을 전달받는다

const myButton = document.querySelector('#my-button');

function handleClick() {
  console.log('버튼이 클릭되었습니다.');
  console.log(this); // myButton 요소 출력
}

myButton.addEventListener('click', handleClick.bind(myButton));
  • 화살표 함수를 사용하여 이벤트 핸들러 함수를 등록하는 경우 ⇒this는 해당 함수를 감싸고 있는 스코프의 this를 따른다,this가 항상 이벤트를 발생시킨 DOM 요소를 가리키도록 보장할 수는 없다 event.target속성을 사용하여 이벤트를 발생시킨 DOM 요소에 대한 참조를 얻는 것이 좋다

 

 

const myButton = document.querySelector('#my-button');

myButton.addEventListener('click', () => {
  console.log('버튼이 클릭되었습니다.');
  console.log(this); // 전역 객체(window) 출력
});

 

드래그앤 드롭

  • drag and dropAPI를 사용한다dragstart, drag, dragenter, dragleave, dragover, drop 등의 이벤트를 제공
  • 개체를 드래그 가능하게 만들려면 해당 요소에서 ”draggable=true”를 설정해야한다
  • 이벤트 핸들러 함수에서 event.preventDefault()를 호출하여 브라우저의 기본 동작을 막아야 한다 ⇒브라우저에서 요소를 드래그하면 기본적으로 페이지가 새로고침되거나 링크가 열리는 등의 동작을 막기 위한 것(기본값은 드롭 작업을 취소한다)
class DOMHelper {
  static clearEventListeners(element) {
    const clonedElement = element.cloneNode(true);
    element.replaceWith(clonedElement);
    return clonedElement;
  }

  static moveElement(elementId, newDestinationSelector) {
    const element = document.getElementById(elementId);
    const destinationElement = document.querySelector(newDestinationSelector);
    destinationElement.append(element);
    element.scrollIntoView({ behavior: "smooth" });
  }
}

 

clearEventListeners(element 주어진 요소에서 모든 이벤트 리스너를 제거하는 데 사용)

  • cloneNode() 메소드를 사용하여 주어진 요소를 복제 true 매개변수는 하위 요소와 모든 하위 요소의 이벤트 리스너를 포함한 모든 자식 요소를 복제하도록 지시한다
  • replaceWith() 메소드를 사용하여 원래 요소를 복제된 요소로 대체하여 반환한다

moveElement(elementId, newDestinationSelector) 주어진 ID를 가진 요소를 newDestinationSelector로 이동시키는 데 사용

  • getElementById() 사용하여 주어진 ID를 가진 요소를 가져 온다
  • querySelector() 를 사용하여 새로운 목적지 요소를 가져 온다
  • append()를 사용하여 이동할 요소를 목적지에 추가


    scrollIntoView() 메소드를 사용하여 이동한 요소가 브라우저의 뷰포트에 표시되도록 스크롤하고 { behavior: "smooth" } 매개변수는 부드러운 스크롤 동작을 사용하도록 지시
element.scrollIntoView();
element.scrollIntoView(alignToTop); // Boolean parameter
element.scrollIntoView(scrollIntoViewOptions); // Object parameter

true : element 요소의 상단을 기준으로 스크롤을 이동한다.

false : element 요소의 하단을 기준으로 스크롤을 이동한다.

behavior : 전환 에니메이션 정의 (auto || smooth)

block : 수직 정렬 (start || center || end || nearest)

inline : 수평 정렬 (start || center || end || nearest)

 

 

 

 

 

class Component {
  constructor(hostElementId, insertBefore = false) {
    if (hostElementId) {
      this.hostElement = document.getElementById(hostElementId);
    } else {
      this.hostElement = document.body;
    }
    this.insertBefore = insertBefore;
  }

  detach() {
    if (this.element) {
      this.element.remove();
      // this.element.parentElement.removeChild(this.element);
    }
  }

  attach() {
    this.hostElement.insertAdjacentElement(
      this.insertBefore ? "afterbegin" : "beforeend",
      this.element
    );
  }
}
  • ID가 제공되면 document.getElementById를 사용하여 호스트 엘리먼트를 찾고 ID가 제공되지 않으면 document.body 엘리먼트를 호스트 엘리먼트로 사용한다
  • insertBefore 매개 변수를 받고, true로 설정할 경우 새로운 엘리먼트를 hostElement의 첫번째 자식 노드 앞에 삽입하고, 그렇지 않으면 마지막 자식 노드 뒤에 삽입한다.
  • detach()는 현재 element를 가지고 있을 경우 해당 엘리먼트를 DOM에서 삭제
  • insertAdjacentElement(position, element)를 사용하여 beforeend 또는 afterbegin 위치에 삽입할 수 있다. 이것은 호스트 엘리먼트의 새로운 자식 엘리먼트로 추가된다
  • 'beforebegin': targetElement자기 앞에.
  • 'afterbegin': 의 첫 번째 자식 바로 앞 targetElement.
  • 'beforeend': 의 targetElement마지막 자식 바로 뒤에 
  • 'afterend': 자체 뒤에 targetElement.



class Tooltip extends Component {
  constructor(closeNotifierFunction, text, hostElementId) {
    super(hostElementId);
    this.closeNotifier = closeNotifierFunction;
    this.text = text;
    this.create();
  }

  closeTooltip = () => {
    this.detach();
    this.closeNotifier();
  };

  create() {
    const tooltipElement = document.createElement("div");
    tooltipElement.className = "card";
    const tooltipTemplate = document.getElementById("tooltip");
    const tooltipBody = document.importNode(tooltipTemplate.content, true);
    tooltipBody.querySelector("p").textContent = this.text;
    tooltipElement.append(tooltipBody);

    const hostElPosLeft = this.hostElement.offsetLeft;
    const hostElPosTop = this.hostElement.offsetTop;
    const hostElHeight = this.hostElement.clientHeight;
    const parentElementScrolling = this.hostElement.parentElement.scrollTop;

    const x = hostElPosLeft + 20;
    const y = hostElPosTop + hostElHeight - parentElementScrolling - 10;

    tooltipElement.style.position = "absolute";
    tooltipElement.style.left = x + "px"; // 500px
    tooltipElement.style.top = y + "px";

    tooltipElement.addEventListener("click", this.closeTooltip);
    this.element = tooltipElement;
  }
}

생성자 함수는 상위 클래스인 컴포넌트 클래스의 생성자 함수를 호출하고, closeNotifier, text, 그리고 create() 함수를 호출한다

  • closeTooltip() 툴팁을 닫을 때, detach() 함수를 호출하고, closeNotifier() 함수도 호출
  • create() 툴팁을 담을 div 엘리먼트를 만들고, 템플릿 엘리먼트를 가져와서 복사한 후, 텍스트를 삽입,툴팁의 위치를 계산하여 설정 이벤트 리스너를 추가하고, 생성된 툴팁 엘리먼트를 this.element에 할당




class ProjectItem {
  hasActiveTooltip = false;

  constructor(id, updateProjectListsFunction, type) {
    this.id = id;
    this.updateProjectListsHandler = updateProjectListsFunction;
    this.connectMoreInfoButton();
    this.connectSwitchButton(type);
    this.connectDrag();
  }

  showMoreInfoHandler() {
    if (this.hasActiveTooltip) {
      return;
    }
    const projectElement = document.getElementById(this.id);
    const tooltipText = projectElement.dataset.extraInfo;
    const tooltip = new Tooltip(
      () => {
        this.hasActiveTooltip = false;
      },
      tooltipText,
      this.id
    );
    tooltip.attach();
    this.hasActiveTooltip = true;
  }
    //어떤요소가 드래그 된건지 연결
  connectDrag() {
    document.getElementById(this.id).addEventListener("dragstart", (event) => {
      event.dataTransfer.setData("text/plain", this.id);
      event.dataTransfer.effectAllowed = "move";
    
    });
  }

//.....
  }
}

DataTransfer.setData() 드래그 조작의 설정 drag data 지정된 데이터 유형. 주어진 유형에 대한 데이터가

존재하지 않으면,types 목록 의 마지막 항목 이 새로운 유형이되도록 드래그 데이터 저장소의 끝에 추가하고

주어진 유형에 대한 데이터가 이미 존재하는 경우 기존 데이터가 동일한 위치에 교체 한다

같은 유형의 데이터를 교체 할 때 types 목록 의 순서는 변경되지 않는다

dataTransfer.effectAllowed ()

  • none: 허용되는 효과가 없음을 의미합니다. 드래그 소스는 이동할 수 없다
  • copy: 드롭 대상에게 복사, 드래그 소스의 원본은 그대로 유지
  • link: 드롭 대상에게 링크, 일반적으로 드롭 대상이 URL이거나 웹 페이지 일 때 사용
  • move: 드롭 대상으로 이동, 드래그 소스의 원본은 제거
  • copyMove: 드롭 대상에 복사되거나 이동, 드래그 소스의 원본은 그대로 유지될 수도, 제거될 수도 있다
  • linkMove: 드롭 대상에게 링크되거나 이동,일반적으로 드롭 대상이 URL이거나 웹 페이지 일 때 사용




class ProjectList {
  projects = [];
  constructor(type) {
    this.type = type;
    const prjItems = document.querySelectorAll(`#${type}-projects li`);
    for (const prjItem of prjItems) {
      this.projects.push(
        new ProjectItem(prjItem.id, this.switchProject.bind(this), this.type)
      );
    }
    console.log(this.projects);
    this.connectDroppable();
  }

  //드롭가능한 이벤트 리스닝
  connectDroppable() {
    //li는 삭제해주고 ul태그에대한 엑세스가 생긴다
    const list = document.querySelector(`#${this.type}-projects ul`);
    //id 읽는건 불가능,유형만 알수 있다
    list.addEventListener("dragenter", (e) => {
      if (e.dataTransfer.types[0] === "text/plain") {
        //드롭이 가능한지 알수 있는 시각적 요소 추가
        list.parentElement.classList.add("droppable");
        e.preventDefault();
      }
    });
    //dragover꼭 있어야 하고 preventDefault를 호출해야함
    list.addEventListener("dragover", (e) => {
      if (e.dataTransfer.types[0] === "text/plain") {
        e.preventDefault();
      }
    });
    list.addEventListener("dragleave", (e) => {
      if (e.relatedTarget.closest(`#${this.type}-projects ul`) !== list) {
        list.parentElement.classList.remove("droppable");
      }
    });
    list.addEventListener("drop", (e) => {
      const prjId = e.dataTransfer.getData("text/plain");
      if (this.projects.find((p) => p.id === prjId)) {
        return;
      }
      document
        .getElementById(prjId)
        .querySelector("button:last-of-type")
        .click();
      list.parentElement.classList.remove("droppable");
      // event.preventDefault(); // not required
    });
  }

dragenter 이벤트는 드래그한 요소가 해당 목록 위에 진입할 때 발생

  • dataTransfer.types를 확인하여 드래그한 요소의 타입이 "text/plain" 인 경우에만 드롭 가능한 시각적 요소(.droppable 클래스를 가진 부모 요소)를 추가하고, 이벤트의 기본 동작을 막는다

dragover이벤트는 드래그한 요소가 해당 목록 위에 있을 때 발생

dragleave이벤트는 드래그한 요소가 해당 목록에서 벗어날 때 발생

e.relatedTarget.closest e.relatedTarget 요소의 가장 가까운 조상 요소를 찾는 JavaScript 메소드이다

  • relatedTarget.closest를 사용하여 드래그한 요소가 해당 목록에 속해 있는지 확인하고, 해당 목록 밖으로 벗어날 경우 시각적 요소를 제거

drop이벤트는 드래그한 요소를 해당 목록에 드롭할 때 발생

  • dataTransfer.getData를 사용하여 드래그한 요소의 id 값을 가져와 this.projects 배열에서 해당 id값을 가진 항목이 이미 존재하는지 확인
  • 이미 존재하는 경우에는 이벤트의 기본 동작을 막는다
  • 존재하지 않을 경우에는 해당요소의 버튼을 클릿하여 해당 요소를 해당목록으로 이동시키고 시각적 요소를 제거

 

 

 

728x90
반응형
profile

Kimsora✨

@sorarar

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그

WH