SFC Vue3 Components in Bitrix, using Composition API, no builders, no CDN and NPM packages

This article will show a non-standard implementation of Vue + Bitrix components, which was not shown in this video and in other similar ones.

Thank you very much to the Bitrix developers for the integration, but as many have already written about this integration: “It is only in its infancy,” in general, yes, I agree. Without Bitrix CLI it will be difficult to write a full-fledged SPA site on Bitrix, and especially who needs it? This will be pure hell for any Frontend developer; it would be easier to use Bitrix as an API, but that’s not the point.

I found a way + – to write normally without crutches SFC Vue components on Bitrix, maybe someone has already done this, and maybe not, nevertheless, this article may help someone.

We will not use CDN, but will use integrated Vue, at least because the Bitrix developers were able to integrate it well for SFC components.

First, let's connect Vue itself.

<?Bitrix\Main\UI\Extension::load("ui.vue3");

I would like to immediately make a reservation for those who are not in the know, the Composition API appeared only in version 3 of Vue. That's why we connect it. Regular Vue will work too, but you'll have to use the Options API instead of the Composition API.

This version 3 extension appeared in the relatively new version of Bitrix, so don’t forget to update!

After we have connected Vue, we then proceed to develop the Vue component.


SCRIPT.JS

/** This function is called when the DOM is ready. */
BX.ready(() => {
    /**
     * Returns an element by its ID
     * @param {string} id - The ID of the element to be returned
     * @returns {HTMLElement} The element with the specified ID, or null if no element with the specified ID exists
     */
    const node = BX('app')
    const application = BX.Vue3.createApp({
      /**
       * This function is a lifecycle hook that is called when the component is first created.
       * It sets up the component's state by returning an object that contains the component's data.
       * @returns {object} An object containing the component's data
       */
      setup() {
        /**
         * Returns a reactive reference to a value.
         * @param {any} value - The value to be referenced
         * @returns {import('vue').Ref<any>} A reactive reference to the value
         */
        const counter = BX.Vue3.ref(0);

        setInterval(() => {
          counter.value++;
        }, 300);

        return {
          counter,
        };
      },
    });
    /**
     * Mounts the Vue 3 application instance to the DOM element.
     * @param {HTMLElement|String} node - The DOM element to mount Vue 3 application instance
     */
    application.mount(node);
})

and many have now noticed who have already tried to write Vue components that we do not have a template property in the object that we pass to createApp.

I promised that we would write without crutches?) So we will be without crutches.


TEMPLATE.PHP

<div id="app">
    {{ counter }}
</div>

Yes, that's right, we write markup in template.php as if we were working in a Vue file.

Well, accordingly, in style.css we mark up the styles for our component, and it even looks without crutches, I would say.

It’s just too beautiful for Bitrix.

And I will say that it can be even more beautiful.


More beautiful…

100% everyone noticed that we are accessing through the Bitrix BX.Vue3 object and it still doesn’t look nice, to put it mildly, despite the fact that we made the SFC component without crutches. Without webpack, without Vite, without Bitrix CLI and other tinsel.

But it still looks so-so, but even here we have a lot of variations on how to do it, and here everyone will decide for themselves what they like.

If the project uses Vue everywhere, then let’s connect Vue globally. Conditionally in header.php

<?Bitrix\Main\UI\Extension::load("ui.vue3");

This means that the BX.Vue3 object is everywhere, therefore we will connect another script globally. And we will do the following operations in this script.

for (const key in BX.Vue3) window[key] = BX.Vue3[key]

Yes, we’ll just move the functions from BX.Vue3 to the window, and the native Vue3 syntax will become available to us

and that means we can rewrite our script.js

BX.ready(() => {
    const node = BX('app')
    const application = createApp({
      setup() {
        const counter = ref(0);

        setInterval(() => {
          counter.value++;
        }, 300);

        return {
          counter,
        };
      },
    });
    application.mount(node);
})

No need for applause, I just clogged your window. But as a solution to the problem, it will do. After which you can optionally remove BX.Vue3 from the window, since we have already removed all the functions.

But many will ask. What if I don’t use Vue everywhere, and I don’t want to clutter the window, but I want to use Vue functions without BX.Vue3?

So (someone has already guessed) there is a way out here too, and a very simple one at that.

We use destructuring for this.

const { ref, createApp } = BX.Vue3

And it will still work great for us, only now these functions are not in the window object

And yes, this method is also suitable for a global script, and your functions will be in the window object, but as constants.

Here is the complete script code of how it will look inside the component

BX.ready(() => {
    const { ref, createApp } = BX.Vue3
    const node = BX('app')
    const application = createApp({
      setup() {
        const counter = ref(0);

        setInterval(() => {
          counter.value++;
        }, 300);

        return {
          counter,
        };
      },
    });
    application.mount(node);
})

Okay, we figured out the component. Let's move on to more difficult things. Front + back connection.

The first question for many is how can we get the data from the back that we received in component.php.

There is an option like this. Not the most beautiful but it will work.

We need to add a script tag to template.php and place the following code there.

const arResult = <?=Bitrix\Main\Web\Json::encode($arResult)?>

or like this, whichever is more convenient for you, I personally like the first method, supposedly you know Bitrix well and use its API

const arResult = <?=json_encode($arResult, true)?>

Or for completely old people, this method is, but as for me it is already a thing of the past.

<?CUtil::PhpToJSObject
  

And now the coolest option. Literally NEXT GEN

Since we wrote our own component anyway. Because native bitrix components work without Vue. I suggest moving from component.php to class.php

So that it would be really cool.

We create our component from scratch and write it in class.php, and do + – something more complicated than reactive timers.

TEMPLATE.PHP

<ul id="users-list" class="users-list">
    <li v-for="(user, key) in arResult.users" class="users-list__item" :key="key">
        {{ user }}
        <button type="button" @click="deleteUser(user.id)">Удалить пользователя</button>
    </li>
    <form @submit="addUser" class="form">
        <div class="form__field">
            <label>Имя пользователя</label>
            <input type="text" name="name">
        </div>
        <button type="submit">Добавить нового пользователя</button>
    </form>
</ul>

We create our template, with Vue logic, in this example there will be an example of adding a user and deleting a user (not from the database, but from an array in the class, but you’re smart – you can adjust everything to suit yourself)

Let's add some styles to style.css so that everyone can be sure that the styles work

.users-list {
    padding: 12px;
}
.users-list__item {
    padding: 12px;
    background-color: #e2e2e2;
    border-radius: 4px;
    margin-bottom: 8px;
}

Next we move on to the most interesting

CLASS.PHP

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
    die();

use Bitrix\Main\UI\Extension,
    Bitrix\Main\Engine\Contract\Controllerable;

Extension::load("ui.vue3");
class UsersList extends CBitrixComponent implements Controllerable
{
    protected static array $users = [
        [
            'id' => 1,
            'name' => 'Alexander',
        ],
        [
            'id' => 2,
            'name' => 'Ivan',
        ],
        [
            'id' => 3,
            'name' => 'Sergey',
        ],
        [
            'id' => 4,
            'name' => 'Smith',
        ],
        [
            'id' => 5,
            'name' => 'John'
        ],
    ];
    public function configureActions(): array
    {
        return [];
    }

    public function deleteUserAction($id): array
    {
        foreach (self::$users as $key => $user) {
            if ($user['id'] == $id) {
                unset(self::$users[$key]);
                break;
            }
        }
        return self::$users;
    }

    public function getUsersAction(): array
    {
        return self::$users;
    }

    public function addUserAction(): array
    {
        $lastUser = end(self::$users);
        $newUser = [
            'id' => $lastUser['id'] + 1,
            'name' => $_POST['name'],
        ];
        self::$users[] = $newUser;
        return self::$users;
    }

    public function executeComponent(): void
    {
        $this->includeComponentTemplate();
    }
}

Here we just connect our Vue, then create a class and inherit from the standard CBitrixComponent and implement an interface so that our class is controlled and we can work with it through BX.ajax.runComponentAction, so as not to go beyond the component and create an array of users with which we will work . Next we move on to the script.

SCRIPT.JS

const { runComponentAction } = BX.ajax;
const { ref, createApp, reactive } = BX.Vue3;
BX.ready(() => {
  createApp({
    setup() {
      function deleteUser(id) {
        const request = runComponentAction("test:test", "deleteUser", {
          mode: "class",
          data: { id: id },
        });
        request.then((reponse) => arResult.users = reponse.data);
      }

      function addUser(e) {
        e.preventDefault();
        const request = runComponentAction("test:test", "addUser", {
          mode: "class",
          data: new FormData(e.target),
        });
        request.then((reponse) => arResult.users = reponse.data);
      };

      const arResult = reactive({});
      const request = runComponentAction("test:test", "getUsers", {
        mode: "class",
      });
      request.then((reponse) => (arResult.users = reponse.data));

      return {
        arResult,
        addUser,
        deleteUser
      };
    },
  }).mount("#users-list");
});

Then everything is simple, let’s create our Vue application, create the objects, variables, functions we need and work with them.

At the very beginning, we create a reactive object arResult into which we will place our other arrays, or objects, this object will monitor changes in it and change the page in accordance with our logic in template.php.

This is what we should get on the page.

This is what we should get on the page.

Accordingly, by clicking on the button to delete a user, he will be deleted from our page, if added, the user will be added. Just because we are deleting from a static array, when adding a user after deleting, we will still get the user who was deleted (well, it’s clear from the code).

You can come up with a lot of examples of how to work with this, in any case it is worth noting that it is quite convenient and fast.

Also, for convenience, you can connect VueDev Tools; they are also quite convenient to connect out of the box.

conclusions

SFC components are very convenient to develop, we are unlikely to reach SPA at this rate, we need to wait for better integration, or use Bitrix as an API and don’t worry, but if there is no such option. This method is not bad at all.

Advantages:

  1. There is no need to configure package.json, install node and node_modules in Bitrix, everything is implemented using “native tools”

  2. You don't need to know the Bitrix CLI to create your own extension that will contain a Vue application.

  3. The template layout is not placed in JS, it is rendered by the server, which means that if you have some text, the page in theory will not be marked as empty by the bot

  4. It is possible to use the COMPOSITION API, which is more productive than the previous API + there is less code and it has become more readable

Flaws:

  1. It is unlikely that you will be able to create another small component separately and use it.

  2. It will not be possible to create an adequate SPA application. (Well, more accurately, you can, but you won’t want to)

I don't think it turned out bad

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *