Score:0

How would one make a variable in a module's JS editable via admin menu?

cn flag

Say I have a library that is attached to a page that brings along with it some JavaScript. For this example, let's say the JavaScript has a variable which is the path to an image that the JS displays. This would look something like:

variable.setAttribute('src', drupalSettings.path.baseUrl + pathToPicture)

Now let's say I want to have pathToPicture configurable via Admin Menu so that one may add their own path to their own picture, instead of using the one that is defaulted to.

How might one go about accomplishing this task? I know the basics of setting up a form and adding it to the admin menu, but I don't know enough to complete something like this on my own. If there is existing documentation on this sort of procedure, I'd appreciate being directed towards it. Otherwise any ideas are welcome. Thanks!

leymannx avatar
ne flag
You can pass variables from PHP to JS (drupalSettings) from a preprocess hook for example. So have your image stored somewhere, maybe user profile, then preprocess into the render array where you need the JS, get the current user, get their image, get the image URL and pass it to drupalSettings.
No Sssweat avatar
ua flag
Here is an idea: Please provide your form's code and the process hook code if you already have it.
apaderno avatar
us flag
Since the question says *so that one may add their own path to their own picture* part, how would you achieve it, and how using JavaScript code would make that possible? With the code shown in the answer, you get a value passed from PHP to JavaScript, but how would users change that value without having access to the settings form? If they have access to the form, they just submit the form.
apaderno avatar
us flag
If I wanted to let end-users set a value that is then used from my module, I would create a form they can access to set that value. My code would then use that value where necessary. I would not need to write JavaScript code to achieve that.
Score:2
de flag

Imagine a module called Configurable Picture. You want the path to the image to be configurable through the admin UI. A path is a string, so the first thing to do is define the configuration for the path. In this case, the configuration object will be configurable_picture.settings path_to_picture.

configurable_picture/config/schema/configurable_picture.schema.yml:

configurable_picture.settings:
  type: config_object
  label: 'Configurable Picture Settings'
  mapping:
    path_to_picture:
      type: string
      label: 'Path to Picture'

The next step is to create a configuration form that allows you to edit the configuration item defined in the above schema. Here is the documentation on creating configuration forms: https://www.drupal.org/docs/drupal-apis/configuration-api/working-with-configuration-forms

With the above steps, you will be able save the path to the picture on the configuration page you have created.

There are two steps required to provide that configuration setting to the JavaScript file. The first is to add a dependency to core/drupalSettings to your JS library.

configurable_picture/configurable_picture.libraries.yml:

picture_form:
  js:
    path/to/file.js: {}
  dependencies:
    - core/drupalSettings

Then, you attach the library and the settings to the form:

$form['#attached']['library'][] = 'configurable_picture/picture_form';
// The configurableSettings key will become the namespace within the 
// drupalSettings object in the JS file.
$form['#attached']['drupalSettings']['configurablePicture'] = [
  // Set the path based on the value saved in configuration:
  'pathToPicture' => \Drupal::config('configurable_picture')->get('path_to_picture'),
];

Then path/to/file.js can access the settings object like this:

(function ($, Drupal, drupalSettings) {

  function getPathToPicture() {
    return drupalSettings.configurablePicture.pathToPicture;
  }

  // Or, from the settings object in Drupal.behaviors:
  Drupal.behaviors.configurablePicture = {
    attach: function (context, settings) {
      console.log(settings.configurablePicture.pathToPicture);
    }
  };
}(jQuery, Drupal, drupalSettings));
Joseph avatar
cn flag
Thanks for this. I've used drupalSettings before to do this sort of communication so I think that part will be easy. I updated my original post to show what my current file structure looks like (minus the JS file), because even though I believe I followed instructions correctly, no form is appearing in the config page. Do you see anything obviously wrong with my code?
Jaypan avatar
de flag
I've answered your original question, on how to spin a configuration variable out to the JS. Your issue with your config form not working is a new topic, and you should open a new thread for that. Drupal answers is a one-question one-answer forum, new questions get new threads.
Jaypan avatar
de flag
Also, you should post your actual code, instead of replacing everything with mymodule. Too easy to miss typos when you aren't showing the actual code.
Joseph avatar
cn flag
Okay, sorry I'm very new to this and was worried about bogging down the forum with multiple questions, especially if they have simple solutions.
Jaypan avatar
de flag
It's all good - we all go through the exact same thing you did when we come here! Welcome to Drupal Answers, and we're here to answer questions on Drupal, so feel free to open topics to ask questions.
Joseph avatar
cn flag
Hey @Jaypan, I have a question about your answer. What is .pathToFile? I can't find documentation on it or get it to work. I receive this error in the console: Cannot read properties of undefined (reading 'pathToFile'). Am I misunderstanding what you're doing?
Jaypan avatar
de flag
You have to replace that with the actual path to your JS file. So if you have `file.js` in the `js` folder of your module, you would put `js/file.js: {}`.
Joseph avatar
cn flag
Yeah sorry I got that, I meant when you use it here, for example: `console.log(settings.configurablePicture.pathToFile);` .pathToFile is supposed to be replaced with a path to the JS file? That seems weird?
Jaypan avatar
de flag
Correct. `settings` contains the settings for the current `context`. These are the two variables passed to Drupal behaviors `.attach` method. On initial document load, `context` is the entire document, and `settings` is the entire `drupalSettings` object. But Drupal behaviors is also called when new elements are inserted into the DOM with Ajax, and `settings` contains the the settings for the new `context`. You can refer still refer to `drupalSettings`, however it contains both the settings from the initial page load, as well as the ajax load, making it less streamlined.
Jaypan avatar
de flag
I should add, `settings.configurablePath.pathToFile` is what is replaced with the file path. Assuming that's a path as you've described, you would need the Drupal root prepended: `settings.path.baseUrl + settings.configurablePath.pathToFile`.
Joseph avatar
cn flag
So this is what it's supposed to look like? `console.log(drupalSettings.path.baseUrl + settings.pathToSound.js/configurablePicture.js);`? That still doesn't make much sense to me, why am I linking the JS file when I'm already in the JS file?
Jaypan avatar
de flag
No,that will be replaced with the path to the picture, not the JS file. You are correct that this code goes in the JS file. Whatever value you have entered into the form on the configuration page will be in `ssettings.configurablePath.pathToPicture`
Jaypan avatar
de flag
Also, the call to console.log is simply so you are able to see the value in your JS console, you don't need it to execute your code. It's a debugging tool. The only part that is relevant is `settings.configurablePath.pathToFile`
Joseph avatar
cn flag
Okay I can see where the confusion is. I thought I had to replace the `.pathToFile` part of `settings.configurablePath.pathToFile` to something else. I see what you're saying now. Still, using `settings.configurablePath.pathToFile` gives me this error: `drupal.js?v=9.3.9:16 Uncaught TypeError: Cannot read properties of undefined (reading 'pathToFile')` because I don't think in your example pathToFile is ever passed to the JS file via an attachment? Would that be why it's not working?
Jaypan avatar
de flag
Sorry, `pathToFile` was a typo. It's supposed to be `pathToPicture`. I'll update the post.
Joseph avatar
cn flag
I figured, thank you for your help. I think there might me mix up between configurable_picture and configurablePicture in the form attachments as well, but I'm not sure. Last question about your code; the library and settings you're attaching to the form go into buildForm, but what would the submitForm look like? My pathToPicture isn't getting communicated properly between the form and the JS file
Jaypan avatar
de flag
I don't understand the question. The library is attached - what does this have to do with the submit handler?
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.