diff --git a/docs/developing/index.rst b/docs/developing/index.rst index 10a94f7..17075c8 100644 --- a/docs/developing/index.rst +++ b/docs/developing/index.rst @@ -20,6 +20,13 @@ If you are considering software development to customize Arches, please read the django-devs/orm-start +.. toctree:: + :caption: Orientation for Frontend Developers + :maxdepth: 2 + + vue/arches-vue-integration + vue/arches-vue-styleguide + .. toctree:: :caption: Reference Guide :maxdepth: 2 diff --git a/docs/developing/vue/arches-vue-integration.md b/docs/developing/vue/arches-vue-integration.md deleted file mode 100644 index 47e08e8..0000000 --- a/docs/developing/vue/arches-vue-integration.md +++ /dev/null @@ -1,425 +0,0 @@ -# Arches Vue Integration Styleguide - -## Table of Contents - -- [Purpose](#purpose) -- [Audience](#audience) -- [Basis for this Style Guide](#basis-for-this-style-guide) -- [Contributions](#contributions) -- [Integrating a Vue Component](#integrating-a-vue-component) -- [Single-Responsibility Principle and Component Decomposition](#single-responsibility-principle-and-component-decomposition) -- [Directory Structure](#directory-structure) -- [Cascading Style Sheets (CSS)](#cascading-style-sheets-css) -- [Importing Components and Component Pathing Shorthand](#importing-components-and-component-pathing-shorthand) -- [TypeScript and ESLint](#typescript-and-eslint) -- [Internationalization (i18n)](#internationalization-i18n) -- [Composition API and Single-file Components](#composition-api-and-single-file-components) -- [Testing](#testing) -- [Example Arches Vue Component Integration](#example-arches-vue-component-integration) - ---- - -## Purpose - -The purpose of this style guide is to establish a unified coding style and set of conventions that all contributors should adhere to when writing code for Arches. By following these guidelines, we aim to: - -- Improve code readability and maintainability -- Facilitate collaboration among developers -- Enhance the overall quality and consistency of Arches software, Arches projects, and Arches applications - -## Audience - -This style guide is intended for developers of all levels who contribute to Arches. Whether you are a seasoned developer or a newcomer to the project, this document will provide you with the necessary guidance to write clean, consistent, and high-quality code. - -## Basis for this Style Guide - -This style guide for Arches is built on top of the standard Vue.js and TypeScript style guides. As such, it inherits and extends the conventions and best practices outlined in those guides. - -Any coding style, formatting, or conventions not explicitly covered in this document should be referenced from the official Vue.js and TypeScript style guides. It's important to maintain consistency with these standard guidelines to ensure compatibility and familiarity for developers working with Vue.js and TypeScript projects. - -For Vue.js, you can refer to the official style guide [here](https://vuejs.org/style-guide/). Similarly, for TypeScript, you can refer to the official TypeScript style guide [here](https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html). - -Please consult these references for any conventions or guidelines not addressed in this style guide. - -## Contributions - -This style guide is a living document that evolves over time. We welcome contributions from the community to improve and expand this guide further. If you have suggestions, feedback, or would like to contribute to the style guide, please reach out to us via the [Arches Forum](https://community.archesproject.org/). - ---- - -## Integrating a Vue Component - -When integrating Vue-based views, plugins, or reports into the Arches framework, developers should utilize the `createVueApplication` function provided at `utils/create-vue-application`. This function is specifically designed to facilitate the integration of Vue components within the Arches environment, ensuring seamless compatibility and optimal performance by abstracting interactions with the i18n API and various current and future Vue plugins, such as PrimeVue. - -However, it's important to note that while the `createVueApplication` function is suitable for integrating most Vue-based components, widgets require different render states and are not yet supported. As such, developers should exercise caution when attempting to integrate widgets using this function. We hope to resolve this in the near future. - -Example: - -```js -import createVueApplication from 'utils/create-vue-application'; -import MyVueApplication from '@/MyVueApplication.vue'; - -createVueApplication(MyVueApplication).then(vueApp => { - vueApp.mount('#my-vue-application-mounting-point'); -}); -``` - -In the provided example, the createVueApplication function, imported from `utils/create-vue-application`, streamlines the integration process by handling the necessary setup and configuration steps, allowing developers to focus on building the core functionality of their Vue application. - -The `createVueApplication` function takes a Vue component (`MyVueApplication` in this case) as its argument, representing the root component of the Vue application. This component encapsulates the entire application logic and user interface. - -Once the Vue application is created using `createVueApplication`, it returns a Vue application instance (`vueApp`), which can then be further manipulated or customized as needed. In the example, the `vueApp` instance is mounted to a specific element in the DOM with the mount method, using the CSS selector `#my-vue-application-mounting-point` to identify the mounting point. - ---- - -## Single-Responsibility Principle and Component Decomposition - -In Vue development, it's crucial to adhere to the Single Responsibility Principle (SRP) and practice component decomposition to ensure that Vue components remain maintainable and scalable. The SRP dictates that each Vue component should have a single responsibility or purpose. By focusing on doing one thing and doing it well, components become easier to understand, modify, and maintain. Following the SRP leads to more modular, reusable, and testable code. - -Component decomposition involves breaking down complex Vue components into smaller, more focused units, with each unit responsible for a specific task or feature. This practice aligns with the SRP and promotes clean, maintainable codebases. - -Example: - -```vue - - - -``` - -In this example: - -- The `UserProfile` component is decomposed into three smaller components: `UserProfileHeader`, `UserProfileBio`, and `UserProfileActions`. -- Each smaller component has a specific responsibility: - - `UserProfileHeader`: Renders the user's name and email. - - `UserProfileBio`: Renders the user's bio. - - `UserProfileActions`: Renders user actions (e.g., buttons for editing the profile). -- The user reactive reference is defined using ref() within the ` - - -``` - -In this example, we import the `useGettext` function from vue3-gettext and destructure the `$gettext` method from the returned object. We then use `$gettext` to translate strings within our template, such as "Foo!" and "Bar!". The translations are managed by vue3-gettext and the `create-vue-application` component, and are dynamically loaded based on the selected locale. - -Before using internationalization features, it's important to run the following commands to extract and compile translations: - -Extract Translations: Run `npm run gettext:extract` to extract translations from the source code and generate template .pot files. -Compile Translations: Run `npm run gettext:compile` to compile translated .po files into machine-readable .json files that can be used by the application. - -For further information, please reference the [vue3-gettext documentation](https://github.com/jshmrtn/vue3-gettext) - ---- - -## Composition API and Single-file Components - -We strongly suggest utilizing the Composition API and Single-file Components for building new features or rewriting existing components into Vue. - -### Composition API - -The Composition API is a way of organizing and reusing logic within Vue components. It allows developers to encapsulate related logic into reusable composition functions, making it easier to manage complex component logic and share code between components. - - -### Single-file Components - -Single-file Components are a feature of Vue that allows developers to define templates, script, and styles in a single `.vue` file. This approach promotes a more modular and cohesive structure for Vue components, making it easier to manage component-specific logic, styles, and templates in a single file. - -### Example -Here's an example of a Single-file Component that uses the Composition API, notice the - - - - - -``` - ---- - -## Testing - -To ensure the reliability and functionality of our Vue components, we use `vitest` for our testing framework. `vitest` is a fast, modern testing framework that provides a comprehensive suite of tools for writing, running, and debugging tests. - -### Why Vitest? - -- **Speed**: `vitest` is designed to be fast, reducing the time it takes to run tests and increasing productivity. -- **Integration**: We plan on eventually migrating our frontend bundler to `vite`, and already having `vitest` in-place will ensure a smooth transition. -- **Modern Features**: `vitest` supports the latest JavaScript features, ensuring compatibility with contemporary development practices. - -### Writing Tests -**Ensure your tests are placed alongside your components, and have a `.spec.ts` suffix.** - -When writing tests for your Vue components, consider the following best practices: - -- **Isolation**: Test each component in isolation to ensure that issues can be traced back to specific units of code. -- **Coverage**: Aim for high test coverage to ensure all paths, including edge cases, are tested. -- **Readability**: Write clear and concise tests that are easy to understand and maintain. - -Here is an example of how to write your tests: - -```vue - - - - - - - -``` - -```js -// src/ExampleComponent.spec.ts - -import { describe, it, expect } from 'vitest'; -import { mount } from '@vue/test-utils'; - -import ExampleComponent from '@/ExampleComponent.vue'; - -describe('ExampleComponent', () => { - it('renders correctly', () => { - const wrapper = mount(ExampleComponent); - expect(wrapper.exists()).toBeTruthy(); - }); - - it('renders h1 element with correct text', () => { - const wrapper = mount(ExampleComponent); - expect(wrapper.find('h1').text()).toBe('Hello from the template!'); - }); - - it('applies scoped styles to h1 element', () => { - const wrapper = mount(ExampleComponent); - const h1 = wrapper.find('h1'); - expect(h1.classes()).toContain('header'); - }); -}); - -``` - -### Running tests - -To run your tests, use the vitest command in your terminal: - -```bash -npm run vitest -``` - -**By following this documentation, you can set up and maintain comprehensive unit and component testing that catches bugs early and improves code quality. For further details, refer to the [Vitest documentation](https://vitest.dev/).** - ---- - -## Example Arches Vue Component Integration - -Below is an example of creating a new plugin for Arches. This example includes import patterns, TypeScript patterns, internationalization, the composition API, single-file components, and Vue/Knockout integration - -```js -// media/js/views/components/plugins/my-plugin.js - -import ko from 'knockout'; -import MyPlugin from '@/plugins/MyPlugin.vue'; -import createVueApplication from 'utils/create-vue-application'; -import MyPluginTemplate from 'templates/views/components/plugins/my-plugin.htm'; - - -ko.components.register('my-plugin', { - viewModel: function() { - createVueApplication(MyPlugin).then(vueApp => { - vueApp.mount('#my-plugin-mounting-point'); - }); - }, - template: MyPluginTemplate, -}); - -``` - -```htm -// templates/views/components/plugins/my-plugin.htm - -
-``` - -```vue -// src/plugins/MyPlugin.vue - - - - - - -``` diff --git a/docs/developing/vue/arches-vue-integration.rst b/docs/developing/vue/arches-vue-integration.rst new file mode 100644 index 0000000..5794340 --- /dev/null +++ b/docs/developing/vue/arches-vue-integration.rst @@ -0,0 +1,105 @@ +############################ +Arches Vue Integration Guide +############################ + +This guide explains how to embed a Vue 3 application into Arches using the +``createVueApplication`` helper. This function bootstraps your app with: + +- Arches frontend internationalization (i18n) via vue3-gettext +- PrimeVue + default theme +- Common services (confirmation, dialogs, toasts) +- Shared directives (tooltips, focus trap, scroll animations) +- Automatic dark-mode theme switching + +Supported Use Cases +=================== + +- **Full-page views** (reports, dashboards) +- **Standalone plugins** +- **Knockout-embedded components** + +Not yet supported: Arches “widgets” that require specialized render context. + +Quick Start +=========== + +1. **Import** your root component and the helper: + + .. code-block:: js + + import createVueApplication from 'utils/create-vue-application' + import MyVueApp from '@/my_project/MyVueApp.vue' + +2. **Initialize** and **mount**: + + .. code-block:: js + + createVueApplication(MyVueApp) + .then(app => { + // Optional: register extra plugins or globalProperties here + app.mount('#my-vue-mount-point'); + }) + .catch(err => { + console.error('Vue integration failed:', err); + }) + +3. **Ensure** your Arches template contains the mount point: + + .. code-block:: html + + +
+ +Knockout Integration Example +============================ + +.. code-block:: vue + + // src/my_project/MyPlugin.vue + + + + + + + +.. code-block:: js + + // media/js/views/components/plugins/my-plugin.js + + import ko from 'knockout'; + import createVueApplication from 'utils/create-vue-application'; + import MyPlugin from '@/my_project/MyPlugin.vue'; + import template from 'templates/views/components/plugins/my-plugin.htm'; + + ko.components.register('my-plugin', { + viewModel: function() { + createVueApplication(MyPlugin) + .then(vueApp => vueApp.mount('#my-plugin-mount')) + .catch(console.error) + }, + template: template + }); + +.. code-block:: html + + + +
\ No newline at end of file diff --git a/docs/developing/vue/arches-vue-styleguide.rst b/docs/developing/vue/arches-vue-styleguide.rst new file mode 100644 index 0000000..f16135d --- /dev/null +++ b/docs/developing/vue/arches-vue-styleguide.rst @@ -0,0 +1,1143 @@ +###################### +Arches Vue Style Guide +###################### + +Table of Contents +================= + +- `Purpose`_ +- `Basis for Style Guide`_ +- `Contributions`_ +- `Structure and Naming`_ + - `File and Folder Naming Conventions`_ + - `Top-Level Structure`_ + - `Component Folder Hierarchy`_ +- `Component Structure`_ + - `Single-File Components`_ + - `Component Decomposition`_ + - `Passing Data`_ +- `The + + + + + +- **Why?** + - **Encapsulation**: All component-related code is in one place, making it easier to understand and maintain. + - **Separation of concerns**: Each section (template, script, style) has its own purpose, improving readability. + +Component Decomposition +----------------------- + +- Components should be decomposed into smaller, reusable components whenever possible. Aim for a single responsibility per component. + + .. code-block:: shell + + src/ + └── project_name/ + └── widgets/ + └── CustomWidget/ + ├── components/ + │ ├── CustomWidgetEditor.vue + │ └── CustomWidgetViewer.vue + └── CustomWidget.vue + +- **Why?** + - **Reusability**: Smaller components can be reused in different contexts, reducing code duplication. + - **Maintainability**: Easier to understand and modify smaller components than large monolithic ones. + - **Testing**: Smaller components are easier to test in isolation. + +Passing Data +------------ + +- **Fetch Proximity** + - Fetch data in the component that actually renders it. Don't lift network calls higher than needed. + + .. code-block:: vue + + + + + + + + + .. code-block:: vue + + + + + + + + + + + + + + + - **Why?** + - **Encapsulation**: Data-fetch logic lives alongside the view that consumes it. + - **Limited prop drilling**: Minimizes passing data through unrelated parents. + - **Error isolation**: Failures are handled locally, without cascading side effects. + +- **Primitives First** + - Pass simple values (strings, numbers, booleans, small arrays/objects) instead of entire model objects whenever possible. + + .. code-block:: vue + + + + + + + + - **Why?** + - **Explicit API**: Readers, tools, and developers see exactly which fields the component needs. + - **Immutable flow**: Primitives can't be mutated in place, preserving one-way data flow. + - **Efficient updates**: Changes to unused object properties won't force re-renders. + +- **Derived State** + - If a component's sole responsibility is to derive or summarize data pass the raw data and let it compute internally. + + .. code-block:: vue + + + + + + - When multiple children need the same computed value, derive once in the parent and pass primitives to avoid duplication and ensure consistency. + + .. code-block:: vue + + + + + + - **Why?** + - **Performance**: Avoids recomputing derived values in multiple components. + - **Predictable props**: Child components receive only the exact values they need. + - **Consistency**: Ensures every consumer uses the same computed values, preventing drift. + +- **Event Emission** + - Emit semantic events (kebab-case) with typed payloads: + + .. code-block:: vue + + + + - **Why?** + - **Explicit contracts**: Consumers know exactly what events to expect and how to handle them. + - **Type safety**: TypeScript ensures the payload matches the expected structure. + +- **Slots** + - Use scoped slots for maximum flexibility; name them clearly to indicate their purpose. + + .. code-block:: vue + + + + - **Why?** + - **Flexibility**: Consumers can customize the rendering of specific parts of the component. + - **Separation of concerns**: Slots allow for a clear distinction between the component's structure and its content. + +The + + + + + - **Why?** + - **TypeScript support**: Enables full TypeScript support directly within each component. + - **Scope safety**: All variables and functions are scoped to the component, preventing accidental global pollution. + +- **Function Declarations** + - Use named `function` declarations for component methods; **do not** use anonymous/arrow functions or function expressions. + - Use of anonymous/arrow functions is allowed for inline callbacks (e.g., `setTimeout`, `Promise.then`, `filter`, `onMounted`, `computed`, etc.). + + .. code-block:: js + + + const incrementCount = () => { count.value++ }; + + + const incrementCount = function() { count.value++ }; + + + function incrementCount() { count.value++ } + + + setTimeout(() => { count.value++ }, 1000); + + - **Why?** + - **Hoisting**: Named functions are hoisted, allowing them to be called before their declaration in the code. This can help avoid issues with function order and improve readability. + - **Debugging**: Named functions provide better stack traces and error + +- **Constants & Literals** + - Declare fixed values in `SCREAMING_SNAKE_CASE`. + - Declare all string literals and magic numbers as named constants. + + .. code-block:: js + + // Bad: magic number and string literal + function calculateTotal(price) { + return price * 0.0825; + } + + function isOrderComplete(order) { + return order.status === 'PENDING'; + } + + // Good: named constants + const TAX_RATE = 0.0825; + const ORDER_STATUS_PENDING = 'PENDING'; + + function calculateTotal(price) { + return price * TAX_RATE; + } + + function isOrderComplete(order) { + return order.status === ORDER_STATUS_PENDING; + } + + - **Why?** + - **Readability**: Named constants make the code more self-explanatory and easier to understand and debug. + - **Maintainability**: Changing a single constant is easier than searching for all occurrences of a magic number or string literal. + +- **Naming Conventions** + - Use descriptive identifiers; avoid single-letter names. + + .. code-block:: js + + // Bad: single-letter naming + function doubleValue(x) { return x * 2; } + + // Good: descriptive naming + function doubleValue(value) { return value * 2; } + + - **Why?** + - **Clarity**: Descriptive names provide context and meaning, making the code easier to read and understand. + - **Maintainability**: Clear names help future developers (or yourself) quickly grasp the purpose of variables and functions. + +- **Modularity & Reuse** + - Extract non-UI logic (data transformations, business rules) into composables or utility modules. + + .. code-block:: js + + // Bad: non-UI logic in component + function calculateDiscount(price, discount) { + return price - (price * discount); + } + + // Good: non-UI logic in utility module + import { calculateDiscount } from '@/my_project/utils/discounts.ts'; + + - **Why?** + - **Separation of concerns**: Keeps UI logic separate from business logic, making components easier to read and maintain. + - **Reusability**: Composables and utility modules can be reused across multiple components, reducing code duplication. + +- **Side-Effects & Async Handling** + - Avoid performing side-effects (API calls, timers, storage access, data formatting, etc.) at module import in + + - **Why?** + - **Predictability**: Side-effects should only occur in controlled environments (e.g. lifecycle hooks) to avoid unexpected behavior. + - **Error handling**: Wrapping async operations in try/catch allows for graceful error handling and user feedback. + +- **Type Safety** + - Import and use explicit types; avoid use of the `any` type. Annotate all function return types. + + .. code-block:: js + + // Bad: using any type + function fetchData(): any { + return fetch('/api/data').then(response => response.json()); + } + + // Good: explicit type annotation + interface User { + id: number; + name: string; + } + + function fetchData(): Promise { + return fetch('/api/data').then(response => response.json()); + } + + - **Why?** + - **Type safety**: Using explicit types helps catch errors at compile time, reducing runtime issues. + - **Documentation**: Type annotations serve as documentation for function behavior and expected input/output. + +Import Pathing +-------------- + +- **Use project alias** (`@/…`) for all local imports; avoid raw relative paths. e.g. + + .. code-block:: js + + // Bad: raw relative path + import { fetchData } from '../../utils/fetch-data.ts'; + + // Good: project alias + import { fetchData } from '@/project_name/utils/fetch-data.ts'; + +- **Why?** + - **Readability**: Project aliases make it clear where the module is located without needing to trace relative paths. + - **Maintainability**: Avoids issues with deep nesting and makes it easier to refactor or reorganize the project structure. + +Import Order +------------ + +- Import lines should be grouped and ordered as follows: + 1. **Vue core** + 2. **Third-party modules** + 3. **Third-party Vue components** + 4. **External Arches Vue components** + 5. **Local Vue components** + 6. **External Arches utilities/composables** + 7. **Local utilities/composables** + 8. **Third-party types** + 9. **External Arches types** + 10. **Local types** + +.. code-block:: vue + + + +Declaration Order +----------------- + +- Within your ` + +The