CSS & Javascript

As Concord CRM is a single page application and relies on JavaScript, you will likely need to write JavaScript code and Vue Single File Components (SFC) to create a proper module that displays information to the user.

With the module creation, a package.json file is provided, which can be used as a good starting point for your module development. To get started, navigate to the module directory via your terminal, e.g., cd modules/[ModuleName], and install the dependencies using the following command:

npm install

This will ensure to install all the required npm packages for the module development, the node_modules folder will be placed in the module root directory.

Please note that you should not commit the node_modules folder to Git or include it in the .zip archive for the end customer.

Using Vite

Concord CRM is using Vite to build all the npm packages and run a dev server for Hot Module Replacement (HMR) during development.

First, ensure that the vite entry points are registered in your module service provider:

use Modules\Core\Facades\Innoclapps;

protected function setup() : void
{
    Innoclapps::vite('resources/js/app.js', 'modules/'.$this->moduleNameLower().'/build');
}

If you used the module:make command, the scaffolding already created a vite.config.js and registered the module entry points in your module service provider.

To start the dev server, navigate to your module directory e.q. modules/[ModuleName] and run the following command:

npm run dev

The command will start a dev server for HMR (Hot Module Replacement), if you are not familiar with Vite, refer to Laravel official Vite documentation.

Making Requests

You can use Innoclapps.request() to perform XHR requests to backend registered routes, when using Innoclapps.request() you will get a pre-configured Axios instance for Concord CRM.

Innoclapps.request("/invoices/1"); // defaults to Innoclapps.request().get('/invoices/1')
Innoclapps.request().post("/invoices");

Router

Concord CRM uses Vue Router to provide routing capabilities for the front-end. If needed, you can utilize the powerful features of Vue Router to manage navigation and routing within your Concord CRM modules.

<template>{{ $route.path }}</template>
<script setup>
    import useRoute from "core/route";
    import useRouter from "core/router";

    const route = useRoute();
    const router = useRouter();
</script>

Registering Routes

You can register custom routes for the module in the module app.js entry point:

// modules/[ModuleName]/resources/js/app.js

import { translate } from "core/i18n";

import MyIndexComponent from "./views/MyIndexComponent.vue";

if (window.Innoclapps) {
    Innoclapps.booting(function (app, router) {
        router.addRoute({
            path: "/invoices",
            component: MyIndexComponent,
            meta: {
                title: translate("modulename::group.key"),
            },
        });
    });
}

Components

To check all of the available globally available components, you should examine the modules/Core/resources/js/components.js file. Note that some components are registered within a plugin; in this case, examine the plugin export file to determine which components are registered.

Please note that by referring to components, we mean Vue Single File Components (SFC).

Registering Global Components

To register a globally available component within the module, you can achieve this in the module app.js file as shown below:

// modules/[ModuleName]/resources/js/app.js

import MyComponent from "./components/MyComponent.vue";

if (window.Innoclapps) {
    Innoclapps.booting(function (app, router) {
        app.component("MyComponent", MyComponent);
    });
}

Localizing Components

Please refer to the Getting Started Guide on more information about localization.

To use the localization text you add in your language group files during development, you will need to generate a language file specifically for the front-end. Concord CRM already includes the language keys in the front-end, but you need to ensure they are generated using the following command:

php artisan translator:json

While developing your module, you may need to execute this command a few times until you have finished localizing the components. However, when shipping the module, no additional steps are needed to generate this language file as Concord CRM handles it.

To localize text in a Vue component, follow the namespace::group.key convention by using the $t globally available Vue component function.

<template>{{ $t('invoices::group.key') }}</template>

<script setup></script>

To use the translation function in a Vue composition API, you will need to use the use the useI18n function.

const { t } = useI18n();

async function createInvoice() {
    await form.post("/invoices");

    Innoclapps.success(t("invoices::invoice.created"));
}

Toast Notifications

To display toast notifications, you should use Innoclapps.success(), Innoclapps.error() or Innoclapps.info(), these functions allow you to provide instant feedback to users in a consistent and user-friendly manner.

Innoclapps.success("That was great");
Innoclapps.error("That failed");
Innoclapps.info("This is great");

The first argument of the notification function is the actual toast message that will be displayed to the user.

You can customize the duration of the toast alert by providing a second argument that specifies how many milliseconds the alert should be displayed.

Innoclapps.success("That was great", 60000); // 6 seconds

Global Variables

Globally provided variables can be retrieved by using Innoclapps.scriptConfig(). This method allows you to access configuration data provided by the server-side scripts in your JavaScript code, ensuring a seamless integration of back-end data into your front-end logic.

const userId = Innoclapps.scriptConfig("user_id");

In the module service provider, you can provide any additional script config variables:

use Illuminate\Support\Facades\Auth;
use Modules\Core\Facades\Innoclapps;

/**
 * Configure the module.
 */
protected function setup(): void
{
    Innoclapps::whenReadyForServing(function () {
        Innoclapps::booting(function () {
            $this->shareDataToScript();
        });
    });
}

/**
 * Share module data to script.
 */
protected function shareDataToScript(): void
{
    Innoclapps::provideToScript(fn () => Auth::check() ? [
        'invoices' => [
            'statuses' => collect(InvoiceStatus::cases())->map(
                fn (Status $status) => ['label' => $status->label(), 'value' => $status->value]
            ),
        ],
    ] : []);
}

To retrieve the shared statuses or any other configuration data that you have added to the script config, you can use the corresponding key. This method ensures that your front-end components can dynamically access and utilize the data provided by the back-end.

const statuses = Innoclapps.scriptConfig("invoices.statuses");

You should check if there is logged-in user before sharing any sensitive data to the front-end.

Confirming Actions

There are few ways in Concord CRM to confirm actions on the front-end.

Confirming Actions via Functions

To display a confirmation dialog before specific action is executed e.q. a delete button is clicked, you can use the globally available function confirm in the Innoclapps instance.

<template>
    <IButton @click="deleteInvoice">{{ $t('core::app.delete') }}</IButton>
</template>
<script setup>
    async function deleteInvoice() {
        await Innoclapps.confirm();

        Innoclapps.request().delete("/invoices/1");
    }
</script>

Confirming Actions In Templates

The template globally available $confirm function can be used to display a confirmation dialog e.q. on button click.

<template>
    <IButton @click="$confirm(()=>deleteInvoice())">
        {{ $t('core::app.delete') }}
    </IButton>
</template>
<script setup>
    function deleteInvoice() {
        Innoclapps.request().delete("/invoices/1");
    }
</script>

Confirming Actions By Changing Button Text

This approach will change the button text to "Confirm" and will set a red background color. After the second click on the button, the confirmed event will be executed.

<template>
    <IButton confirmable @confirmed="deleteInvoice">
        {{ $t('core::app.delete') }}
    </IButton>
</template>
<script setup>
    function deleteInvoice() {
        Innoclapps.request().delete("/invoices/1");
    }
</script>

Confirming Actions In Dropdown Items

This approach will change the dropdown item text to "Confirm" and will set a red background color. After the second click on the item, the click event will be executed.

<template>
    <IDropdown>
        <IDropdownItem confirmable @click="deleteInvoice">
            {{ $t('core::app.delete') }}
        </IDropdownItem>
    </IDropdown>
</template>
<script setup>
    function deleteInvoice() {
        Innoclapps.request().delete("/invoices/1");
    }
</script>

Using CSS

Concord CRM utilizes Tailwind CSS for its front-end styling framework. This approach allows developers to apply a wide range of utility-first CSS classes directly within the module components. As a result, you can easily leverage any of the pre-defined CSS classes that Concord CRM has incorporated into its existing components to maintain a consistent look and feel throughout the application.

Limitations

Tailwind CSS purges unused classes from the generated CSS file. If you use a class that Concord CRM has never used in its components, the CSS styles of this class won't be included. Currently, it's not possible to overcome this limitation.

For such cases, you should either use a Concord CRM component that is already available depending on your use case or write your own custom CSS in the module resources/css/app.css file.

In the module vite.config.js, the input configuration should look like this:

input: [
    __dirname + '/resources/css/app.css',
    __dirname + '/resources/js/app.js',
],

Then in the module resources/js/app.js include the CSS file at the top:

import "../css/app.css";

Finally the module service provider, ensure to register the app.css Vite entry point.

Innoclapps::vite(
    ['resources/js/app.js', 'resourcs/css/app.css'],
    'modules/'.$this->moduleNameLower().'/build'
);