Kendo React 에 대한 기록

juhojung·2022년 2월 8일
0

react.js

목록 보기
1/4

매우 하드한 라이브러리라 기록도 하드하니 주의

회사에서 Telerik 컴포넌트를 사용해보며 특이했던, 어려웠던 케이스를 작성해봅니다.

  1. WebEditor 사용시 한글 입력 이슈.
    telerik은 ProseMirror 라는 에디터 라이브러리가 사용되고있다
    WebEditor 특성상 유저가 입력하는 컨텐츠를 web element tag 로 변환하는데,
    이때에 select, option, input 등의 tag 에서 문제가 발생함.
import { getAttributes, hasAttrs, getAttrs , hole} from "./schema_utils";

export const input = {
    attrs: {
        value: { default: "" },
        style: { default: null },
        contenteditable : {default : true},
        type : {default : null},
        checked : {default : false},
        id : {default : null},
        placeholder : {default : ""}
    },
    group:"block",
    content : "inline*",
    selectable: false,
    parseDOM: [ {
        tag: 'input',
        getAttrs: (dom:any) => ({
            value : dom.getAttribute('value'),
            style: dom.getAttribute('style'),
            contenteditable : dom.getAttribute('contenteditable'),
            type : dom.getAttribute('type'),
            checked : dom.getAttribute('checked'),
            id : dom.getAttribute('id'),
            placeholder : dom.getAttribute('placeholder')
        })
    } ],
    toDOM: (node:any) => {
        return [ 'input', getAttrs(node.attrs) ];
    }
}

위 코드는 예시중 하나인데, input tag 에 해당 노드를 덧붙여서 처리하려고 만든것이다.
보면 attribute 가 들어가는것을 몇몇 항목들에대해 제어하고있다.

let nodes = schema.spec.nodes.addToEnd("select", select);
nodes = nodes.addToEnd("option", option);
nodes = nodes.addToEnd("textarea", textarea);
nodes = nodes.addToEnd("input", input);

적용은 위처럼 하면된다.

그럼이제 한글 입력 이슈를 처리해보자. 아래는 전체 코드

new EditorView({
            mount : event.dom
        },
        {
            ...event.viewProps,
            state : EditorState.create({ doc : doc, plugins }),
            handleDOMEvents: {
                // https://prosemirror.net/docs/ref/#view.EditorProps.handleDOMEvents
                ...event.viewProps.handleDOMEvents,
                keydown: (_view : any, event:any) => {
                    const { code, target, ctrlKey } = event;
                    return (
                    (code === "Backspace" ||
                        code === "Delete" ||
                        (ctrlKey && code === "KeyA")) &&
                    target.nodeName === "INPUT"
                    );
                },
                input: (view : any, event:any) => {
                    const target = event.target;
                    try{
                        if (!event.isComposing && target.nodeName === "INPUT") {
                            const cursor = target.selectionStart;
                            const parent = target.parentNode;
                            const index = Array.from(parent.childNodes).indexOf(target);
                            const pos = view.posAtDOM(target);
                            var tr;
                            if(props.editdisabled){
                                return;
                            }
                            tr = view.state.tr.setNodeMarkup(pos-1, null, {
                                value: target.value,
                                type: target.getAttribute("type")
                            });
                            view.dispatch(tr);
                            const input = parent.childNodes.item(index);
                            input.focus();
                            input.setSelectionRange(cursor, cursor);
                        } else if(target.nodeName === "SELECT"){
                            const pos = view.posAtDOM(target);
                            let tr = view.state.tr.setNodeMarkup(pos-1, null, {
                                value : target.value,
                                id : target.id
                            });
                            for(var i=0;i<target.childNodes.length;i++){
                                const pos = view.posAtDOM(target.childNodes[i]);
                                target.childNodes[i].innerText
                                if(target.childNodes[i].value == target.value){
                                    tr = tr.setNodeMarkup(pos-1, null, {
                                        selected : "selected",
                                        value : target.childNodes[i].value
                                    });
                                } else {
                                    tr = tr.setNodeMarkup(pos-1, null, {
                                        value : target.childNodes[i].value
                                    });
                                }
                            }
                            view.dispatch(tr);
                        }
                    }catch(err){console.log(err)}
                }
            }
        });

위코드에서 말하는건 단순한데,
에디터를 만들때 이벤트 핸들링 하는 함수에 추가적인 액션을 넣는다.

한글은 영어와 다르게 하나의 커서안에서 최대 4개의 키를 입력할수도 있는데,
이게 prose-mirror 에디터에는 적용이 안되어있다.

ex: hello -> 5커서, 안녕 -> 2커서, 6번의 타이핑

// <- 현재 타이핑중이라면
event.iscomposing  
// <- editor에서 클릭하여 커서가있는 타겟이 들어온다 이게 input 이라면
target.nodeName == "INPUT" 
// 1. 매번 입력시마다 이전 커서 위치를 가져온다. (입력시마다 커서는 한칸씩 이동된다)
// 2. 현재 커서 위치를 이전 커서위치로 강제로 focus 한다.
const cursor = target.selectionStart;
const parent = target.parentNode;
const index = Array.from(parent.childNodes).indexOf(target);
const pos = view.posAtDOM(target);
var tr;
if(props.editdisabled){
	return;
}
tr = view.state.tr.setNodeMarkup(pos-1, null, {
	value: target.value,
	type: target.getAttribute("type")
	});
view.dispatch(tr);
const input = parent.childNodes.item(index);
input.focus();
input.setSelectionRange(cursor, cursor);

2. Kendo-React-Grid 에서 Custom Cell 적용시 문제

custom cell은 아래와같이 적용하면된다.
그리드 컴포넌트.tsx 예시

<GridContainer .... >
<Column
	key={idx}
	headerClassName={headerClass[idx]}
	className={rowClass[idx]}
	field={raw}
	title={props.titles[idx]}
	filterable={false}
	width={
		keysWidth && keysWidth[idx]
			? keysWidth[idx]
			: undefined
	}
	// minResizableWidth={64}
	cell={(e) => {
		if (props.getCustomEl)
			return props.getCustomEl(
				idx,
				e.dataIndex,
				e.columnIndex,
				e.dataItem,
				e
			);
		return null;
	}}
	locked={isLocked}
/>
</GridContainer>

props.getCustomEl

const getCustomEl = (
        idx: number,
        dataIdx?: number,
        columnIdx?: number,
        dataItem?: any,
        props?: GridCellProps
    ) => {
    	if (dataIdx == undefined) {
	        // 해당 컬럼이 custom cell 로 지정된 컬럼이라면 true
            if (customElIndexes.indexOf(idx) != -1) return true; 
            return false;
        }
        // dataIdx 가 undefiend 가 아닐경우 custom cell return
        return <S.TableTd ...blahblah > </S.TableTd>;
    }

우선 getCustomEl이라는 함수를 만든 이유는
GridComponent <-> MainPage 와같이 데이터가 있는 페이지와 컴포넌트사이에서 전달하기 위함이다.
핵심은 dataIdx 가 없어도 해당 컬럼이 custom cell이라면 true라도 날려줘야하는데,
이유는 한번 false라고 날려준 컬럼은 나중에 데이터가 적용되서 제대로된 엘리먼트를 리턴하더라도
출력이안된다.
(애초에 dataIdx에 undefined 들어오는게 너무웃김..ㅋㅋㅋ)

이외에도 참 어이없는것들이 많은데 추후에 추가해볼 예정.
라이브러리가 잘 만들었으나, develop 이 많이 필요하다.
이럴거면 그냥 material-ui 쓰지.. 괜히 도입해서 개고생이다.

profile
"어찌 할 수 없는 일에 대해 고민하는 시간은 낭비일 뿐이다."

0개의 댓글