매우 하드한 라이브러리라 기록도 하드하니 주의
회사에서 Telerik 컴포넌트를 사용해보며 특이했던, 어려웠던 케이스를 작성해봅니다.
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);
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 쓰지.. 괜히 도입해서 개고생이다.