import { OperatorExecuterAttrRequire } from './OperatorExecuterAttrRequire'; import { RawSet } from '../rawsets/RawSet'; import { Render } from '../rawsets/Render'; import { AfterCallBack, ElementSource, ExecuteState, ReturnContainer, Source } from './OperatorExecuter'; import { ObjectUtils } from '@dooboostore/core'; export class DrAppender extends OperatorExecuterAttrRequire { constructor(rawSet: RawSet, render: Render, returnContainer: ReturnContainer, elementSource: ElementSource, source: Source, afterCallBack: AfterCallBack) { source.operatorAround = undefined; super(rawSet, render, returnContainer, elementSource, source, afterCallBack, false); } async executeAttrRequire(attr: string): Promise { const itRandom = RawSet.drItOtherEncoding(this.elementSource.element, 'DrAppender'); const vars = RawSet.drVarEncoding(this.elementSource.element, this.elementSource.attrs.drVarOption ?? ''); const newTemp = this.source.config.window.document.createElement('temp'); const variableName = this.elementSource.attrs.drVariableNameOption; const itemVariableName = this.elementSource.attrs.drItemVariableNameOption; const itemIndexVariableName = this.elementSource.attrs.drItemIndexVariableNameOption; const itemOffsetIndexVariableName = this.elementSource.attrs.drItemOffsetIndexVariableNameOption; const hasVariableName = !!variableName; const hasItemVariableName = !!itemVariableName; const hasItemIndexVariableName = !!itemIndexVariableName; const hasItemOffsetIndexVariableName = !!itemOffsetIndexVariableName; const indexOffsetOption = this.elementSource.attrs.drItemIndexOffsetOption; const hasIndexOffsetOption = !!indexOffsetOption; ObjectUtils.Script.evaluate( ` try{ ${this.render.bindScript} ${this.elementSource.attrs.drBeforeOption ?? ''} const offset = ${hasIndexOffsetOption ? Number(indexOffsetOption) : 0}; const data = ${ObjectUtils.Path.toOptionalChainPath(attr)}; const attrStr = \`${attr}\`.trim(); const isChunkPath = /\\[\\d+\\]$/.test(attrStr); if (data !== undefined && data !== null) { // SSR 호환성을 위해 Array.from 사용 const items = isChunkPath ? (Array.isArray(data) ? data : [data]) : (typeof data.getAll === 'function' ? data.getAll().slice(offset) : Array.from(data).slice(offset)); var i = -1; for(const it of items) { i++; const userIdx = i + offset; // 아이템 접근 경로 생성 let destItStr = attrStr + '[' + (isChunkPath ? i : userIdx) + ']'; if (!isChunkPath && typeof data.getAll === 'function') { destItStr = '(' + attrStr + '.getAll()[' + userIdx + '])'; } else { destItStr = '(' + destItStr + ')'; } const n = this.__render.element.cloneNode(true); // 옵션 복원 및 정제 Object.entries(this.__render.drAttr).filter(([k,v]) => k !== 'drAppender' && k !== 'drNextOption' && v).forEach(([k, v]) => n.setAttribute(this.__render.drAttrsOriginName[k], v)); // 정교한 치환 로직 (속성) n.getAttributeNames().forEach(itName => { let attrVal = n.getAttribute(itName); attrVal = attrVal.replace(/\\#it\\#/g, destItStr) .replace(/\\#nearForOfIt\\#/g, destItStr) .replace(/\\#index\\#/g, String(i)) .replace(/\\#offsetIndex\\#/g, String(userIdx)) .replace(/\\#nearForOfIndex\\#/g, String(i)); if (${hasVariableName} && '${variableName}') attrVal = attrVal.replaceAll('#${variableName}#', attrStr); if (${hasItemVariableName} && '${itemVariableName}') attrVal = attrVal.replaceAll('#${itemVariableName}#', destItStr); if (${hasItemIndexVariableName} && '${itemIndexVariableName}') attrVal = attrVal.replaceAll('#${itemIndexVariableName}#', String(i)); if (${hasItemOffsetIndexVariableName} && '${itemOffsetIndexVariableName}') attrVal = attrVal.replaceAll('#${itemOffsetIndexVariableName}#', String(userIdx)); n.setAttribute(itName, attrVal); }); // 정교한 치환 로직 (InnerHTML) n.innerHTML = n.innerHTML.replace(/\\#it\\#/g, destItStr) .replace(/\\#index\\#/g, String(i)) .replace(/\\#offsetIndex\\#/g, String(userIdx)); if (${hasVariableName} && '${variableName}') n.innerHTML = n.innerHTML.replaceAll('#${variableName}#', attrStr); if (${hasItemVariableName} && '${itemVariableName}') n.innerHTML = n.innerHTML.replaceAll('#${itemVariableName}#', destItStr); if (${hasItemIndexVariableName} && '${itemIndexVariableName}') n.innerHTML = n.innerHTML.replaceAll('#${itemIndexVariableName}#', String(i)); if (${hasItemOffsetIndexVariableName} && '${itemOffsetIndexVariableName}') n.innerHTML = n.innerHTML.replaceAll('#${itemOffsetIndexVariableName}#', String(userIdx)); if (this.__render.drStripOption === 'true') { Array.from(n.childNodes).forEach(it => this.__render.fag.append(it)); } else { this.__render.fag.append(n); } } // 다음 세대 센티넬 생성 (정석 체이닝) const nextN = this.__render.element.cloneNode(true); Object.entries(this.__render.drAttr).filter(([k,v]) => v).forEach(([k, v]) => nextN.setAttribute(this.__render.drAttrsOriginName[k], v)); const basePath = attrStr.replace(/\\[\\d+\\]$/, ''); const appenderObj = ${ObjectUtils.Path.toOptionalChainPath(attr.replace(/\\[\\d+\\]$/, ''))}; const nextChunkIdx = isChunkPath ? Number(attrStr.match(/\\[(\\d+)\\]$/)[1]) + 1 : (appenderObj?.length ?? 0); nextN.setAttribute('${RawSet.DR_APPENDER_NAME}', basePath + '[' + nextChunkIdx + ']'); nextN.setAttribute('${RawSet.DR_ITEM_INDEX_OFFSET_OPTIONNAME}', (offset + items.length)); this.__render.fag.append(nextN); } ${this.elementSource.attrs.drAfterOption ?? ''} }catch(e){console.error('DrAppender Error:', e);} `, Object.assign(this.source.obj, { __render: Object.freeze({ drStripOption: this.elementSource.attrs.drStripOption, drAttr: this.elementSource.attrs, drAttrsOriginName: RawSet.drAttrsOriginName, fag: newTemp, ...this.render } as Render) }) ); RawSet.drVarDecoding(newTemp, vars); RawSet.drItOtherDecoding(newTemp, itRandom); const template = this.source.config.window.document.createElement('template'); template.innerHTML = newTemp.innerHTML; this.returnContainer.fag.append(template.content); const rr = RawSet.checkPointCreates(this.returnContainer.fag, this.source.obj, this.source.config); this.elementSource.element.parentNode?.replaceChild(this.returnContainer.fag, this.elementSource.element); this.returnContainer.raws.push(...rr); return ExecuteState.EXECUTE; } }