SpreadJS Designer

SpreadJS Designer

-SpreadJS Designer’dan foydalanish va xatolarni bartaraf etish-

Kirish

Men SpreadJS kutubxonasiga ilk bor loyihasiga biriktirilganimda duch kelganman. Ushbu kutubxona Excel fayllarini ekranda ko‘rish va tahrirlash imkonini berish uchun ishlatilgan. Uni qo‘llash jarayonida duch kelgan muammolarni shu yerda jamladim. Ular qatoriga yangilash yoki import qilishda yon panel va bog‘lash yo‘llari yo‘qolib qoladigan xato, shuningdek, mijoz talablarini qondirish uchun qo‘shilgan funksiyalar — varaqlar qo‘shilishining oldini olish va kataklar o‘chirilishining oldini olish — kiradi. Oxir-oqibat, ikkala muammoni hal qilish uchun ham SpreadJS Designer’ning buyruqlar tizimini tushunish talab etildi. Natijada, talablarni amalga oshirish uchun hujjatlashtirilmagan qismlarni mustaqil ravishda tahlil qilishimga to‘g‘ri kelgan tajribaga ega bo‘ldim.

Yangilash yoki qayta so‘rov yuborishda yon panel va bog‘lash yo‘llari yo‘qoladi

Shablon dastlab yuklanganda, Excel oynasi bilan birga yon panelda bog‘lash yo‘llari daraxti ham odatdagidek ko‘rsatilardi. Biroq yangilash tugmasini bosganimda yoki faylni qayta ochganimda, Excel oynasida bog‘lash yo‘llari ko‘rinmay qoldi, yon panel ham yo‘qoldi. Dastlabki yuklash muvaffaqiyatli bo‘ldi, birinchi yangilash muvaffaqiyatsiz tugadi, ikkinchi yangilash esa yana muvaffaqiyatli bo‘ldi. Jarayon toq va juft raqamli urinishlar o‘rtasida aniq almashinadigan namunaga ega edi.

Sabab: checkbox turidagi buyruqlar har chaqirilganda o‘z holatini almashtiradi

Yon panelni chizadigan funksiya workbook.open()o‘zining muvaffaqiyat callback’i ichida chaqirilgan. Konsolga Designer.getCommand(TEMPLATE_DESIGN_MODE) yordamida olingan command obyektini chiqarganimda, unda quyidagi maydon bor edi.

{
  "commandName": "templateDesignMode",
  "type": "checkbox"
}

"type": "checkbox"Bu asosiy jihat edi. SpreadJS Designer’dagi checkbox turidagi buyruqlar lentadagi Bold va Italic tugmalari kabi har chaqirilganda yoqilgan/o‘chirilgan holatlar o‘rtasida almashish uchun mo‘ljallangan. Ushbu buyruq ma’lumotlar har safar yuklanganda bajarilgani sababli, amalda quyidagi ketma-ketlik takrorlanayotgan edi.

Xato aynan "ushbu buyruq nechanchi chaqiruvda bajarilayotganiga" bog‘liq edi, yangilash tugmasining o‘ziga emas. Dastlab, tasvirni chizish vaqti bilan bog‘liq muammo deb o‘ylab, avval setTimeout va suspendPaint/resumePaint sozlamalarini o‘zgartirib ko‘rdim, ammo ularning sababga hech qanday aloqasi yo‘q edi. Alomatlarning toq va juft urinishlar bo‘yicha aniq ajralishi o‘z-o‘zidan "holat qiymati ishtirok etayotganiga" ishora edi, biroq bu signalni darhol anglamaganimdan afsusdaman.

Yechim

Buyruqni bajarishdan oldin avval uning joriy holatini tekshirdim va agar u allaqachon yoqilgan bo‘lsa, bajarilishini o‘tkazib yubordim.

return (designer) => {
  const isAlreadyOn = command.getState?.(designer);
  if (isAlreadyOn) return;
  return execute(designer);
};

Mijoz talablari: varaqlar qo‘shilishi va bog‘langan kataklar o‘chirilishining oldini olish

Funksiyani yakunlash arafasida turganimda, mijoz yana uchta qo‘shimcha talab yubordi.

1. Shablon konfiguratsiyasi ekranida varaqlar qo‘shilishining oldini olish (+ tugmani olib tashlash yoki boshqa usuldan foydalanish orqali)

2. Bog‘lash yo‘llariga ega kataklarning o‘chirilishiga yo‘l qo‘ymaslik. Ushbu katak qiymatlari boshqa ekranda o‘z holicha ishlatilishi kerak edi, shuning uchun ularni olib tashlash mumkin emas. Biroq bog‘lash yo‘llariga ega bo‘lmagan barcha boshqa kataklar hozirgidek erkin tahrirlanishi kerak. Faqat "o‘chirish" bloklanishi lozim.

3. Varaq almashtirilganda, o‘sha varaqdagi bog‘lash yo‘llarini yon paneldagi maydonlar ro‘yxatida aks ettirish.

Varaqlarni almashtirganda yon panelni yangilash

3-talab nisbatan osonlik bilan ActiveSheetChanged hodisasi yordamida hal qilindi. Varaqlar o‘zgarganda, joriy rejimga — fayl import qilish rejimimi yoki mavjud ma’lumotlar bazasiga so‘rov yuborish rejimimi — qarab bog‘lash yo‘llarini qayta ajratib olaman yoki qayta xaritalayman, so‘ng yon panel daraxtini qayta chizadigan funksiyani chaqiraman.

workbook.bind(Events.ActiveSheetChanged, function (sender, args) {
  const currentSheet = args.newSheet;
  if (!currentSheet) return;
  const sheetName = currentSheet.name();

  if (isImport) {
    const bindingPathsFromSheet = extractBindingPathsFromSheet(currentSheet);
    void setBindingPathToData(currentDesigner, currentWorkbook, bindingPathsFromSheet);
  } else {
    const currentSheetDatas = fieldsBySheetMap?.get(sheetName) || [];
    void setBindingPathToData(currentDesigner, currentWorkbook, currentSheetDatas);
  }
});

1- va 2-talablar uchun to‘g‘ri yechimni topgunimga qadar yondashuvimni taxminan uch marta to‘liq qayta ko‘rib chiqishimga to‘g‘ri keldi.

1-yondashuv. Kataklarni qulflash

Avval faqat bog‘lash yo‘llariga ega kataklarni qulflashga, qolgan kataklarni esa tahrirlash mumkinligicha qoldirishga, keyin esa varaq himoyasini yoqishga harakat qildim.

const unlockedStyle = new GC.Spread.Sheets.Style();
unlockedStyle.locked = false;
sheet.setDefaultStyle(unlockedStyle);

for (let r = 0; r < sheet.getRowCount(); r++) {
  for (let c = 0; c < sheet.getColumnCount(); c++) {
    if (sheet.getBindingPath(r, c)) {
      sheet.getCell(r, c).locked(true);
    }
  }
}
sheet.options.isProtected = true;

Amalda qo‘llaganimda, bog‘lash yo‘llariga ega bo‘lmagan kataklarni ham umuman tahrirlab bo‘lmay qoldi. setDefaultStyle dan foydalanganimda ham, butun diapazonni majburan qayta yozganimda ham natija bir xil bo‘ldi. Konsolni tekshirganimda, standart holatda ham qulflashning o‘zi yoqilmaganini aniqladim. Sababni oxirigacha aniq belgilay olmadim, biroq kod orqali darhol aniqlab bo‘lmaydigan alohida himoya parametri shablonning (.sjs) faylining o‘zida allaqachon sozlangan bo‘lishi mumkin, deb taxmin qilaman. Sabab nima bo‘lishidan qat’i nazar, varaqni himoyalash funksiyasi "bog‘lanmagan kataklar butunlay erkin tahrirlanishi, faqat o‘chirish bloklanishi kerak" degan talabga tubdan mos kelmas edi.

2-yondashuv. Buyruqni qayta belgilash

Checkbox turidagi buyruq muammosini hal qilishda bilib olgan buyruqlar tizimidan foydalanishga harakat qildim. O‘chirish amali ham nomga ega buyruq orqali bajariladi, deb taxmin qilib, deleteRows nomli buyruqni commandManager().getCommand() yordamida qidirdim va asl buyruqni saqlab, keyin uni shartli ravishda bloklash orqali qayta belgiladim.

const originalDeleteRows = commandManager.getCommand('deleteRows');
commandManager.register('deleteRows', {
  canUndo: originalDeleteRows.canUndo,
  execute: (context, options, isUndo) => {
    // 대상 행에 바인딩패스가 있으면 차단하는 로직
    return originalDeleteRows.execute(context, options, isUndo);
  }
}, false, false, false, false);

Biroq, hatto o‘ng tugma menyusidagi Delete Row/Column bandini bosganimda ham, qayta aniqlangan execute amalda chaqirilmadi. Rasmiy forumda aniq sababni tasdiqlay olmagan bo‘lsam-da, avval checkbox buyrug‘i bilan ko‘rganimizdek, Designer tasma yoki o‘ng tugma menyusi uchun amallarni o‘zining commandMap tizimi orqali bog‘laydi. Bu o‘ng tugma menyusidagi "Delete Row" bandini bosish commandManager bilan ro‘yxatdan o‘tkazilgan nomlangan buyruqni bevosita chaqirmasligini, balki Designer ichidagi alohida yo‘l orqali qayta ishlanishini anglatadi. Boshqacha aytganda, tashqi ko‘rinishda ular bir xil turdagi "delete" amali bo‘lib ko‘rinsa-da, Delete tugmasi (→ clear turidagi buyruq) hamda o‘ng tugma menyusidagi satr/ustun o‘chirish amali turli yo‘llardan foydalanayotgan edi. Buyruqning o‘zini tadqiq qilishda cheklovlar bor degan xulosaga kelib, yondashuvimni o‘zgartirdim.

3-yondashuv. Kontekst menyusini boshqarish

Buyruq bajarilishini bloklashga urinish o‘rniga, yo‘nalishni o‘zgartirdim: balki o‘ng tugma menyusi ochilganda undagi bitta "Delete" bandini shunchaki o‘chirib qo‘yish mumkin deb o‘yladim. Savolni "Menyu o‘ng tugma orqali ochilganda har safar qaysi hodisa chaqiriladi?" degan shaklga keltirdim va contextMenu.onOpenMenu ni topdim.

Biroq uni o‘zgartirishsiz ulagan vaqtimda callback odatdagidek chaqirildi, ammo itemsDataForShown (amalda ko‘rsatiladigan menyu bandlari ro‘yxati) doimiy ravishda bo‘sh massiv sifatida qayd etildi. Designer tasma va o‘ng tugma menyusini o‘zining commandMap tizimi orqali boshqargani sababli, uni oddiy workbook obyektining contextMenu obyektiga ulash Designer amalda chizadigan bandlarni ushlash uchun yetarli emasdek ko‘rindi. Dastlabki onOpenMenu funksiyasini oldindan saqlab, uni o‘rab olish uchun kodni qayta tashkil qilganimdan so‘ng, bandlar odatdagidek uzatila boshlandi.

workbook.contextMenu.onOpenMenu = function (menuData, itemsDataForShown, hitInfo, spread) {
  let result = true;
  if (typeof originalOnOpenMenu === 'function') {
    result = originalOnOpenMenu.apply(this, arguments);
  }
  const sheet = spread.getActiveSheet();
  const info = hitInfo.worksheetHitInfo;

  if (!info || hitInfo.hitTestType == null) {
    // 시트 탭 영역: insertSheet 항목 비활성화
    itemsDataForShown?.forEach((item) => {
      if (item.name === 'insertSheet') item.disable = item.disabled = true;
    });
  } else if (info.row >= 0 && info.col >= 0 && sheet.getBindingPath(info.row, info.col)) {
    // 셀 영역: 바인딩패스가 있으면 delete 관련 항목 비활성화
    itemsDataForShown?.forEach((item) => {
      const name = item.name?.toLowerCase() ?? '';
      const text = item.text?.toLowerCase() ?? '';
      if (name.includes('delete') || text.includes('delete')) item.disable = item.disabled = true;
    });
  }
  return result;
};

Ushbu mantiq yordamida varaq yorlig‘idagi insertSheet va katakdagi delete bandlarini alohida o‘chirib qo‘ydim va ikkala talabni ham bajardim. Tasmaning + tugmasi bitta parametr yordamida ekrandan butunlay yashirildi: workbook.options.newTabVisible = false; Bu override’dan mustaqil edi. Boshqacha aytganda, o‘ng tugma orqali yangi varaq qo‘shish yo‘li onOpenMenu bilan bloklandi, tugmaning o‘zi esa ushbu parametr orqali o‘chirib qo‘yildi.

Xulosa

Ushbu ish davomida shuni angladimki, SpreadJS Designer bilan ishlaganda ko‘pincha faqat umumiy SpreadJS Workbook API’iga qarab ish ko‘rib bo‘lmaydi. Tasma, Context Menu va Command yagona tizim sifatida bog‘langanligi sababli, biror narsa ishlamay qolganda uni tuzatishning eng tezkor usuli avvalo tegishli buyruq obyektlarini tekshirish edi. Bu tajriba Designer API’laridan shunchaki foydalanishdan tashqari, uning ichki tuzilishini biroz yaxshiroq tushunishimga yordam berdi.

pong

Site footer