View | Details | Raw Unified | Return to bug 37911
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt (+10 lines)
Lines 11-16 Link Here
11
    [% t("Koha staff interface") | html %]
11
    [% t("Koha staff interface") | html %]
12
[% END %]</title>
12
[% END %]</title>
13
[% Asset.css("css/mainpage.css") | $raw %]
13
[% Asset.css("css/mainpage.css") | $raw %]
14
[% Asset.js("js/vue/dist/islands.js") | $raw %]
14
[% INCLUDE 'doc-head-close.inc' %]
15
[% INCLUDE 'doc-head-close.inc' %]
15
</head>
16
</head>
16
17
Lines 301-306 Link Here
301
            </div> <!-- /.col-sm-9 -->
302
            </div> <!-- /.col-sm-9 -->
302
303
303
        </div> <!-- /.row -->
304
        </div> <!-- /.row -->
305
        <div class="row w-25 m-auto p-4 border border-4 border-warning br-4 rounded text-center">
306
            <h1>🚧 Static Page with Vue Components 🚧</h1>
307
308
            <div id="hello-islands" data-component="HelloIslands" data-props='{"message": "Hello from props!"}'></div>
309
        </div> <!-- /.row -->
304
310
305
[% MACRO jsinclude BLOCK %]
311
[% MACRO jsinclude BLOCK %]
306
    <script>
312
    <script>
Lines 309-314 Link Here
309
            $(".news_delete").on("click", function(){
315
            $(".news_delete").on("click", function(){
310
                return confirmDelete(MSG_CONFIRM_DELETE);
316
                return confirmDelete(MSG_CONFIRM_DELETE);
311
            });
317
            });
318
319
            setTimeout(() => {
320
                document.getElementById("hello-islands").dataset.props = JSON.stringify({ message: 'This is a delayed message! Props (data-props) reflect.' });
321
            }, 2000);
312
        });
322
        });
313
    </script>
323
    </script>
314
[% END %]
324
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/HelloIslands.vue (+37 lines)
Line 0 Link Here
1
<template>
2
    <h2>Hello from Islands!</h2>
3
    <p>This component is rendered as an island in a static HTML page.</p>
4
5
    <!-- Display message prop -->
6
    <p v-if="message">{{ message }}</p>
7
8
    <!-- Reactive counter example -->
9
    <p>Counter: {{ count }}</p>
10
    <!-- Koha's bootstrap works in here! -->
11
    <button @click="incrementCounter" class="btn btn-primary">
12
        Increment Counter
13
    </button>
14
</template>
15
16
<script>
17
import { ref } from "vue"
18
19
export default {
20
    props: {
21
        message: {
22
            type: String,
23
            default: "",
24
        },
25
    },
26
    data() {
27
        return {
28
            count: 0,
29
        }
30
    },
31
    methods: {
32
        incrementCounter() {
33
            this.count++
34
        },
35
    },
36
}
37
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/islands.ts (+93 lines)
Line 0 Link Here
1
import { createApp, Component, App } from "vue";
2
3
/**
4
 * A registry for Vue components.
5
 * @type {Map<string, Component>}
6
 */
7
export const componentRegistry: Map<string, Component> = new Map<
8
    string,
9
    Component
10
>();
11
12
/**
13
 * Registers a Vue component with a name.
14
 * @param {string} name - The name of the component.
15
 * @param {Component} component - The Vue component to register.
16
 * @returns {void}
17
 */
18
export function registerComponent(name: string, component: Component): void {
19
    componentRegistry.set(name, component);
20
}
21
22
/**
23
 * Mounts Vue components to DOM elements based on the `data-component` attribute.
24
 * Components are created with props parsed from the `data-props` attribute.
25
 * Watches for changes in props and updates the component accordingly.
26
 * @returns {void}
27
 */
28
export function mountComponents(): void {
29
    console.log("Mounting components");
30
31
    const elements: NodeListOf<Element> =
32
        document.querySelectorAll("[data-component]");
33
    elements.forEach((element: Element) => {
34
        const componentName: string | null =
35
            element.getAttribute("data-component");
36
        if (!componentName) {
37
            console.warn("No data-component attribute found.");
38
            return;
39
        }
40
41
        const component: Component | undefined =
42
            componentRegistry.get(componentName);
43
        if (!component) {
44
            console.warn(`Component ${componentName} not found.`);
45
            return;
46
        }
47
48
        const props: string | null = element.getAttribute("data-props");
49
        const parsedProps: Record<string, any> = props ? JSON.parse(props) : {};
50
51
        // Create and mount the Vue component
52
        const app: App = createApp(component, parsedProps);
53
        app.mount(element);
54
55
        // Watch for updates to props
56
        watchProps(element, app, component);
57
    });
58
}
59
60
/**
61
 * Watches for changes in props and updates the component accordingly.
62
 * @param {Element} element - The DOM element where the component is mounted.
63
 * @param {App} app - The Vue application instance.
64
 * @param {Component} component - The Vue component.
65
 * @returns {void}
66
 */
67
function watchProps(element: Element, app: App, component: Component): void {
68
    const propsAttr: string | null = element.getAttribute("data-props");
69
    let prevProps: Record<string, any> = propsAttr ? JSON.parse(propsAttr) : {};
70
71
    const observer = new MutationObserver(() => {
72
        const newPropsAttr: string | null = element.getAttribute("data-props");
73
        if (newPropsAttr) {
74
            const newProps: Record<string, any> = JSON.parse(newPropsAttr);
75
            if (JSON.stringify(newProps) !== JSON.stringify(prevProps)) {
76
                prevProps = newProps;
77
                app.unmount(); // Unmount existing component
78
                createApp(component, newProps).mount(element); // Mount with new props
79
            }
80
        }
81
    });
82
83
    observer.observe(element, {
84
        attributes: true,
85
        attributeFilter: ["data-props"],
86
    });
87
}
88
89
import HelloIslands from "../components/HelloIslands.vue";
90
registerComponent("HelloIslands", HelloIslands);
91
92
// Automatically mount components when the DOM is fully loaded
93
document.addEventListener("DOMContentLoaded", mountComponents);
(-)a/rspack.config.js (-1 / +18 lines)
Lines 13-18 module.exports = { Link Here
13
            "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts",
13
            "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts",
14
        "admin/record_sources":
14
        "admin/record_sources":
15
            "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts",
15
            "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts",
16
        islands: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/islands.ts",
16
    },
17
    },
17
    output: {
18
    output: {
18
        filename: "[name].js",
19
        filename: "[name].js",
Lines 56-61 module.exports = { Link Here
56
            },
57
            },
57
        ],
58
        ],
58
    },
59
    },
60
    /**
61
    optimization: {
62
        splitChunks: {
63
            chunks: "all",
64
            cacheGroups: {
65
                default: false, // Disable default cache groups to avoid affecting existing bundles
66
                vendors: false, // Disable vendor caching
67
                vue: {
68
                    test: /[\\/]node_modules[\\/]vue[\\/]/,
69
                    name: "vue",
70
                    chunks: "all",
71
                    enforce: true,
72
                },
73
            },
74
        },
75
    },
76
    */
59
    plugins: [
77
    plugins: [
60
        new VueLoaderPlugin(),
78
        new VueLoaderPlugin(),
61
        new rspack.DefinePlugin({
79
        new rspack.DefinePlugin({
62
- 

Return to bug 37911