.. _ApplicationJs:

==========
JavaScript
==========

.. sectionauthor:: Bernhard Posselt <dev@bernhard-posselt.com>

The JavaScript files reside in the **js/** folder and should be included
in the appropriate controller. There is two methods to inject your JavaScript files.

  1. ``Util::addScript``
  2. ``Util::addInitScript``

.. code-block:: php

  /**
   * Add a javascript file
   *
   * @param string $application Your application ID, e.g. 'your_app'
   * @param string $file Your script name, e.g. 'main'
   * @param string $afterAppId Optional, the script will be loaded after this app
   * @param bool $prepend Optional, if true the script will be prepended to this app scripts list
   */
   public static function addScript(string $application, string $file = null, string $afterAppId = 'core', bool $prepend = false): void

  /**
   * Add a standalone init js file that is loaded for initialization.
   * Be careful loading scripts using this method as they are loaded early
   * and block the initial page rendering. They should not have dependencies
   * on any other scripts than core-common and core-main.
   *
   * @param string $application Your application ID, e.g. 'your_app'
   * @param string $file Your script name, e.g. 'main'
   */
   public static function addInitScript(string $application, string $file): void

.. note:: If your script is only needed after a specific event, e.g. after the Files app is loaded,
   you will have to register a Listener in your app ``Appinfo/Application.php``.

Here is an example for the Files app (which emits the ``LoadAdditionalScriptsEvent``).
For more information about app bootstrapping, see the :ref:`application-php` section.

.. code-block:: php

  namespace OCA\YourApp\AppInfo;

  use OCA\Files\Event\LoadAdditionalScriptsEvent;
  use OCA\YourApp\Listener\LoadAdditionalListener;
  use OCP\AppFramework\App;
  use OCP\AppFramework\Bootstrap\IBootContext;
  use OCP\AppFramework\Bootstrap\IBootstrap;
  use OCP\AppFramework\Bootstrap\IRegistrationContext;

  /**
   * @package OCA\YourApp\AppInfo
   */
  class Application extends App implements IBackendProvider, IAuthMechanismProvider, IBootstrap {
    public const APP_ID = 'your_app';

    public function __construct(array $urlParams = []) {
      parent::__construct(self::APP_ID, $urlParams);
    }

    public function register(IRegistrationContext $context): void {
      $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
    }

    public function boot(IBootContext $context): void {}

.. code-block:: php

  namespace OCA\YourApp\Listener;

  use OCA\YourApp\AppInfo\Application;
  use OCA\Files\Event\LoadAdditionalScriptsEvent;
  use OCP\EventDispatcher\Event;
  use OCP\EventDispatcher\IEventListener;
  use OCP\Util;

  class LoadAdditionalListener implements IEventListener {

      public function handle(Event $event): void {
          if (!($event instanceof LoadAdditionalScriptsEvent)) {
              return;
          }

          Util::addInitScript(Application::APP_ID, 'init');
          Util::addScript(Application::APP_ID, 'main', 'files');
      }
  }


Sending the CSRF token
----------------------

If any other JavaScript request library than jQuery is being used, the requests need to send the CSRF token as an HTTP header named **requesttoken**. The token is available in the global variable **OC.requestToken**.

For AngularJS the following lines would need to be added:

.. code-block:: js

    var app = angular.module('MyApp', []).config(['$httpProvider', function($httpProvider) {
        $httpProvider.defaults.headers.common.requesttoken = OC.requestToken;
    }]);


Generating URLs
---------------

To send requests to Nextcloud the base URL where Nextcloud is currently running is needed. To get the base URL use:

.. code-block:: js

    var baseUrl = OC.generateUrl('');

Full URLs can be generated by using:

.. code-block:: js

    var authorUrl = OC.generateUrl('/apps/myapp/authors/1');


Extending core parts
--------------------

It is possible to extend components of the core web UI. The following examples
should show how this is possible.

Extending the "new" menu in the files app
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 9.0

.. code-block:: js

    var myFileMenuPlugin = {
        attach: function (menu) {
            menu.addMenuEntry({
                id: 'abc',
                displayName: 'Menu display name',
                templateName: 'templateName.ext',
                iconClass: 'icon-filetype-text',
                fileType: 'file',
                actionHandler: function () {
                    console.log('do something here');
                }
            });
        }
    };
    OC.Plugins.register('OCA.Files.NewFileMenu', myFileMenuPlugin);

This will register a new menu entry in the "New" menu of the files app. The
method ``attach()`` is called once the menu is built. This usually happens right
after the click on the button.


Loading initial state
---------------------

Often apps have some kind of initial state. Often the first thing a script does
is querying an endpoint to obtain this initial state. This makes the user
experience sub optimal as they have to wait for yet another request to finish
loading.

To provide the initial state in a standardized way quickly to the javascript
Nextcloud provides an API. The API consists of a PHP part (that supplies the state)
and a JS part (that fetches and parses the state).

Providing the initial state with PHP
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Providing state in PHP is done via the ``OCP\AppFramework\Services\IInitialState``. This service
has two methods you can use to provide the initial state.

* ``provideInitialState(string $appName, string $key, $data)``:
  If you know for sure your state will be used. For example on the settings page of your app.
* ``provideLazyInitialState(string $appName, string $key, Closure $closure)``:
  If you want to inject your state on a general page. For example the initial state of the notifications app. The callback will be invoked if and only if a template is rendered.

You call both methods with the name of your app and a key. This is to scope
the states properly. You will need both when retrieving the initial state in
javascript.

The data for the initial state is converted to JSON. So be sure that the
data you provide (either in $data or as a return from the $closure) can be converted
to JSON.

Obtaining the initial state in JavaScript
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To obtain the initial state in your JavaScript you have to only call one
function

- Vue way with `@nextcloud/initial-state <https://github.com/nextcloud/nextcloud-initial-state>`_:

.. code-block:: js

    import { loadState } from '@nextcloud/initial-state'

    const val = loadState('myapp', 'user_preference')

    // Provide a fallback value to return when the state is not found
    const valWithFallback = loadState('myapp', 'user_preference', 'no_preference')

- Legacy way:

.. code-block:: js

    const state = OCP.InitialState.loadState('MyApp', 'MyState');

Now state will contain the provided state which you can use as any variable. It
is as simple as that.

.. _basics_frontend_javascript_keyboard_shortcuts:


Keyboard shortcuts
------------------

In case you want to improve your user experience with keyboard shortcuts, make sure
to not overwrite browser, operating system and other Nextcloud wide shortcuts.
Also there is an accessibility setting for users to opt-out of **any** keyboard shortcuts
Nextcloud wide. You can check the setting with the following function which returns a boolean
(available in Nextcloud 25 and later):

.. code-block:: js

    OCP.Accessibility.disableKeyboardShortcuts();

If that is the case, no additional shortcuts shall be registered by any app. Only ``space``
to toggle checkboxes and ``enter`` to submit the currently active buttons or links are okay,
as any other shortcut might interfere with screenreaders and other accessibility tools.
