Po provedení DOT:Repeater vykreselní canvas

Zdravím,
mám DOT:Repeater, který přidává na stránku prvky <canvas> potřebuji po dokončení spustit JavaScript, který provede vykreslení vygenerovaných prvků <canvas>.

Díky moc

Tady mám JS, který se používá pro prvky, které nejsou v DOT:REPEATER.

// Funkce pro vykreslení do canvasu s použitím barvy z atributu data-vlan
function drawOnCanvas(canvas) {
    var ctx = canvas.getContext('2d');
    var color = canvas.getAttribute('data-vlan');

    var computedColor = getComputedStyle(canvas).getPropertyValue("--color-vlan-" + color);
    ctx.fillStyle = computedColor || 'white';
    ctx.fillRect(6, 7, 38, 25);
    ctx.fillRect(11, 32, 28, 6);
    ctx.fillRect(17, 37, 16, 6);
}

// Výběr všech canvasů s atributem data-vlan a vykreslení na každý z nich
var canvases = document.querySelectorAll('.port_vlan');
canvases.forEach(function (canvas) {
    drawOnCanvas(canvas);
}); 

(předpokládám, že toto je jen zjednodušený příklad; pokud ne a chcete fakt tři obdélníky, bylo by podle mě o dost jednodušší tam dát SVG obrázek, který můžete napřímo napsat v dothtml markupu)

V podstatě máte dvě možnosti jak toho dosáhnout

Knockout binding handler

to je snad dostatečně popsané na Adding interactivity using Knockout binding handlers | DotVVM Documentation. V podstatě na to potřebujete vlastní DotvvmControl, který zavolá knockout binding handler. V tom se vám spustí init vždycky když se přidá tím repeaterem do stránky, případně update když se změní nějaký jeho data binding.

JsComponent

což máme aktuálně zdokumentované jenom pro integraci React komponent. Použít React pro renderování do canvasu je samozřemě nesmysl, ale můžete se celkem snadno napojit do toho spojovacího API. Pořád jsem na to nenapsal dokumentaci, tak to popíšu nejdřív tady (pak to snad přeložím a publikujem to):

  • Potřebujete si vyrobit JavaScriptový view modul, k tomu dokumentace existuje
  • Z modulu vyexportujeme komponentu, která pak půjde použít v dothtml markupu pomocí <js:JménoKomponenty property1={value: Data} /> (taky jí můžete předávat libovolně pojmenované commandy a templaty)
  • DotVVM jako JsComponent prostě očekává funkci, která dá element a property z markupu. Můžete použít následující kostru:

function myControl(
        element: HTMLElement, // wrapper tag (nastavitelný v dothtml propertou `WrapperTagName`)
        props: { [key: string]: any }, // všechny property s value bindingem, resource binding nebo konstantou
        commands: { [key: string]: (args: any[]) => Promise<any> }, // property s commandem, nebo staticCommandem
        templates: { [key: string]: string },
        setProps: (p: { [key: string]: any }) => void // funkce kterou můžete propsat změny zpět do value bindingů
    ): DotvvmJsComponent {

    let canvas = document.createElement("canvas")
    elm.appendChild(canvas)
    update(props)
    
    function update(updatedProps) {
        // vyčtu si potřebná data
        const { property1, property2 } = props
        if (updatedProps.property1) {
            // některé změny je výhodné dělat jen při určitých změnách
        }
        // re-render canvasu bych dělal prostě po každé změně
    }
 

    // DotVVM bude automaticky volat následující události:
    return {
        updateProps(updatedProps) {
            // updatedProps obsahují jen změny, tímto je sloučíme s přechozím stavem
            props = { ...props, ...updatedProps }
            update(updatedProps)
        },
        dispose() {
            // HTML element včetně obsahu se odstraní sám, to zde není potřeba řešit
        }
    }
}
  • Tuto komponentu pak exportujete z view modulu podobně jako ty React komponenty - $controls: { myComponent: { create: myControl } }
  • Ve vaše případě by to bylo něco takového (pokud nepoužíváte typescript, tak prostě smažte ty typy za dvojtečkou)

function vlanCanvas(element: HTMLElement, props: { [key: string]: any }): DotvvmJsComponent {

    let canvas = document.createElement("canvas")
    elm.appendChild(canvas)
    update(props)
    
    function update(updatedProps) {
        const { color } = props
        const ctx = canvas.getContext('2d');
        
        context.clearRect(0, 0, canvas.width, canvas.height)
        const computedColor = getComputedStyle(canvas).getPropertyValue("--color-vlan-" + color)
        ctx.fillStyle = computedColor || 'white'
        ctx.fillRect(6, 7, 38, 25)
        ctx.fillRect(11, 32, 28, 6)
        ctx.fillRect(17, 37, 16, 6)
    }
 

    return {
        updateProps(updatedProps) {
            props = { ...props, ...updatedProps }
            update(updatedProps)
        }
    }
}

export default context => ({
    $controls: { create: vlanCanvas }
})

modul je někde potřeba registrovat jako resource

config.Resources.RegisterScriptModuleFile("vlanCanvasModule", "scripts/vlanCanvasModule.js");

a v markupu pak

@js vlanCanvasModule

...

<js:vlanCanvas color={value ...} />

img onerror

pokud vám to přijde moc opruz, tak bonusová (prokletá) metoda je použít něco takového

<canvas >
<img src="/doesntexist" onerror="drawOnCanvas(event.target.previousElementSibling)"

Omlouvám se pozdní odpověď. Kdyby něco nebylo jasné nebo nefungovalo, tak se ptejte dál, zkusím odpovídat dřív :slight_smile:

Děkuji moc za super vysvětlení.