3

I use the Sapper framework and I want to open my own dev tools window that should have full access to Javascript objects of the main window.

I'm trying to create a new window with a svelte component and with same Javascript context:

var win = window.open('abour:blank')
var container = win.document.createElement('div')
var win.document.body.appendChild(container)
var component = new ComponentClass({
    target: container,
})

It works but without CSS styles.

You can use this REPL for tests.

How can I apply the component CSS styles for the new window?

Or what is the best practices to create such windows?

1 Answer 1

2

I solved this problem by simply transferring all styles from the parent window (see the appendCss function)

If you use sapper you can transfer only these styles: link[rel="stylesheet"][href^="client/"]

Full solution bellow and you can also see this REPL

ComponentWindow.ts

export class ComponentWindow {
    constructor({
        windowName = '',
        windowFeatures = 'width=600,height=400,resizable,scrollbars=yes,status=1',
        replace = false,
    }: {
        windowName?: string,
        windowFeatures?: string,
        replace?: boolean,
    } = {}) {
        this._windowOptions = [ 'about:blank', windowName, windowFeatures, replace ]
    }

    // region create window

    private readonly _windowOptions: any[]
    private _window
    public get window() {
        if (!this.isOpened) {
            this._window = window.open(...this._windowOptions)
            this.appendCss()
            this.appendContainer()
        }
        return this._window
    }

    private appendCss() {
        const {window: _window} = this

        const parentStyleElements = Array.from(window.document.querySelectorAll(
            'link[rel="stylesheet"][href^="client/"], style',
        ))

        for (let i = 0; i < parentStyleElements.length; i++) {
            const parentStyleElement = parentStyleElements[i]
            let styleElement
            switch (parentStyleElement.tagName) {
                case 'LINK':
                    styleElement = _window.document.createElement('link')
                    styleElement.rel = 'stylesheet'
                    styleElement.href = (parentStyleElement as any).href
                    break
                case 'STYLE':
                    styleElement = _window.document.createElement('style')
                    styleElement.id = parentStyleElement.id
                    styleElement.innerHTML = parentStyleElement.innerHTML
                    break
                default:
                    throw new Error('Unexpected style element: ' + styleElement.tagName)
            }
            _window.document.head.appendChild(styleElement)
        }
    }

    private appendContainer() {
        const {window} = this
        window.container = window.document.createElement('div')
        window.document.body.appendChild(window.container)
    }

    // endregion

    // region attachComponent

    private _component
    public attachComponent(componentClass?, options?) {
        let {_component} = this
        if (_component) {
            _component.$destroy()
            this._component = _component = null
        }

        if (!componentClass) {
            return
        }

        const {window} = this
        _component = new componentClass({
            ...options,
            target: window.container,
        })
        this._component = _component

        window.addEventListener('beforeunload', () => {
            this.attachComponent()
        })

        return _component
    }

    // endregion

    public get isOpened() {
        return this._window && !this._window.closed
    }

    public focus() {
        if (this.isOpened) {
            this._window.focus()
        }
    }

    public destroy() {
        this.attachComponent()
        if (this.isOpened) {
            this._window.close()
            this._window = null
        }
    }
}

Usage:

<script>
    import {ComponentWindow} from './ComponentWindow.js'
    import ComponentClass from './ComponentClass.svelte'
    import {onMount, onDestroy} from 'svelte'

    let componentWindow = new ComponentWindow()
    let component
    let value = 10

    onDestroy(() => componentWindow.destroy())

    $: if (component) component.$set({ value })

    async function openComponentWindow() {
        if (componentWindow.isOpened) {
            componentWindow.focus()
            return
        }

        component = await componentWindow.attachComponent(ComponentClass, {
            props: {
                value               
            }
        })

        componentWindow.focus()
    }
</script>

<button on:click="{openComponentWindow}">Open component Window</button><br>
<button on:click="{() => value++}">Change value</button>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.