decaporg / decap-cms

A Git-based CMS for Static Site Generators
https://decapcms.org
MIT License
17.99k stars 3.05k forks source link

Allow specifying a dynamic default value via a function #1407

Open papandreou opened 6 years ago

papandreou commented 6 years ago

- Do you want to request a feature or report a bug?

feature

- What is the current behavior?

The default value of a field has to be a fixed string/number/boolean (depending on the field type).

- What is the expected behavior?

It would be great to be able specify a function that would return the desired value (maybe even allow it to return a promise for it, for even greater flexibility).

My use case is that I would like to generate uuids for my collection entries and editor components, and store them in a field with widget: 'hidden'.

Would be great to be able to do something like:

CMS.registerEditorComponent({
  // ...
  fields: [{
    name: 'uuid',
    label: 'UUID',
    widget: 'hidden',
    default() {
      return generateUuid();
    }
  } /*, ... */ ]
});
erquhart commented 6 years ago

I've been thinking about this recently - we'd need a way to do it for fields in the config.yml, too.

erquhart commented 6 years ago

@papandreou check out my proposal in #1409, you brought up a good case here that helped. The caveat is that we need to get rid of widget: hidden, and instead allow some sort of visible: false property for any widget. That way the dynamic default value function that will ship with widgets can be used automatically.

tech4him1 commented 6 years ago

Related to https://github.com/netlify/netlify-cms/issues/725.

papandreou commented 6 years ago

@erquhart, cool. That approach looks good to me!

papandreou commented 6 years ago

we'd need a way to do it for fields in the config.yml, too

I don't know if it's a standard thing, but I noticed that the yaml parser I use mentions a funky !<tag:yaml.org,2002:js/function> syntax for declaring functions: https://github.com/nodeca/js-yaml#load-string---options-

Might be too weird, though :)

erquhart commented 6 years ago

Yeah, I'm aware of js-in-yaml, and agree that it's weird. Extensions shipping functions feels pretty sane.

tech4him1 commented 6 years ago

A possible implementation for editor components (not config.yml), was created in #1511.

Korka13 commented 6 years ago

Hello,

I don't know if what I'm trying to reach is already possible or it could be related to this request. I have a hugo site deployed on netlify through github, using netlify cms. I would like to set the netlify cms config.yml to return automatically a default field for the post collection. The field is the weight and I want it to count how many posts are published and return a negative number bigger than that, With hugo I use -{{ len (where .Site.Pages "Section" "post") }} in the archetypes but I can't figure out if I can do something similar with netlify cms

Thanks!

papandreou commented 5 years ago

Managed to hack around this by creating a custom uuid widget:

CMS.registerWidget(
  'uuid',
  class extends React.Component {
    static propTypes = {
      onChange: PropTypes.func.isRequired,
      forID: PropTypes.string,
      value: PropTypes.node,
      classNameWrapper: PropTypes.string.isRequired
    };

    static defaultProps = {
      value: ''
    };

    componentDidMount() {
      const { value, onChange } = this.props;

      if (!value) {
        onChange(generateUuid());
      }
    }

    render() {
      const { value, classNameWrapper, forID } = this.props;

      return (
        <span id={forID} className={classNameWrapper}>
          {value}
        </span>
      );
    }
  }
);
erquhart commented 5 years ago

Makes sense - a dedicated id widget might be a good idea to include with the cms, too.

papandreou commented 5 years ago

I guess -- if it's the only use case we can come up with for the dynamic default value feature 😆

erquhart commented 5 years ago

Lol exactly, I still haven't thought of another one

Sent with GitHawk

Undistraction commented 5 years ago

@erquhart Would it be worth thinking about this from a different perspective? Could you add a 'uid' field that supported interpolation in the same way that 'slug' does? They are very similar - both are used once on creation. If this was supported you could do:

uid: '{{year}}-{{month}}-{{day}}-{{hour}}-{{minute}}-{{second}}-{{collection}}-{{slug}}'

You could simplify this by adding a {{dateTime}} interpolation. Another option would be for Netlify CMs to provide its own {{uuid}} interpolation (using uuid):

uid: '{{uuid}}-{{collection}}-{{slug}}'

If you think this is worthwhile I'll happily put together a PR.

erquhart commented 5 years ago

The issue there is consistency and avoiding privileged entities. Frontmatter values are currently always generated by a widget, how do you see your solution working within that context?

Sent with GitHawk

Undistraction commented 5 years ago

I suppose it depends on how you look at the uuid field. It is a field that should never change after creation, so the idea that it needs a widget doesn't really make sense because it's very reason for being is that it should never be edited, and the creation of uuids is pretty standard, so I see no problem with Netlify CMS handling the creation in the same way it handles the creation of the filename, once at creation.

I can see your concerns about this field having unique behaviour, but a uuid field is by definition unique: it should never change. It has to be stored as a field because there is nowhere else to store it, but I don't think that means it has to be treated like other fields. It is a special case. I think there's a good argument for a uuid being generated for everything by default and used for tracking relations.

Anyhow, I think a robust solution is needed, as this is currently quite a big weakness in the library: the fact that relations are based on fields that are not protected from user editing makes the use of relations deeply unstable.

erquhart commented 5 years ago

Definitely agree on the need for it. I think the ideal approach is probably a combination of our suggestions: supporting placeholders in the default field for widgets, and adding a built in uuid placeholder. Then you could do:

{ name: id, widget: hidden, default: "{{uuid}}" }

Sent with GitHawk

Undistraction commented 5 years ago

That sounds like a good solution to me.

I guess the other thing to consider is whether all items that are created should have a uuid field: is this something that should happen by default? This would definitely be a great help when the need for a relation emerges later on.

Undistraction commented 5 years ago

An additional consideration. Because of how a hidden widget is treated it doesn't seem to be possible to use a widget to set a default value, but not show that value within the CMS. For any widget other than 'hidden', an Editor component will be rendered to the UI. In the case of a uuid widget. In short, using a Widget to provide a uuid is far from ideal because it must display that value (or an empty widget) to the user, despite a uuid being an internal fields. I think this is another good reason to pursue #1975.

alexisreina commented 5 years ago

I just came across the same issue, and I agree width @Undistraction that It will better to approach it differently, as the uuid is an internal value that has no meaning for the cms user. As the project I'm working on does not include React, I create an uuid widget like @papandreou but with createClass and no external dependencies ment to be used in script tag. The uuid function is from the nanoid package.

  <!-- Include the script that builds the page and powers Netlify CMS -->
  <script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
  <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
  <script>
    CMS.registerWidget(
      'uuid',
      createClass({
        getDefaultProps: function() {
          return {
            value: ''
          }
        },
        uuid: function(options) {
          options = options || {};
          var size = options.size || 21;
          var url = options.url || "Uint8ArdomValuesObj012345679BCDEFGHIJKLMNPQRSTWXYZ_cfghkpqvwxyz-";
          var id = "";

          if (typeof self === "undefined" || (!self.crypto && !self.msCrypto)) {
            while (0 < size--) {
              id += url[(Math.random() * 64) | 0];
            }
            return id;
          }

          // else
          var crypto = self.crypto || self.msCrypto;
          var bytes = crypto.getRandomValues(new Uint8Array(size));
          while (0 < size--) {
            id += url[bytes[size] & 63];
          }
          return id;
        },
        componentDidMount: function() {
          var value = this.props.value;
          var onChange = this.props.onChange;
          var uuid = this.uuid;

          if (!value) {
            onChange(uuid());
          }
        },
        render: function() {
          var value = this.props.value;
          var classNameWrapper = this.props.classNameWrapper;
          var forID = this.props.forID;

          return h('span', { id: forID, className: classNameWrapper }, value);
        }
      })
    );
  </script>
erquhart commented 5 years ago

Relevant: https://github.com/netlify/netlify-cms/issues/1975#issuecomment-458972246

erquhart commented 5 years ago

I think the bigger issue here is that widgets need to set their own default values, the CMS has no way of doing that correctly.

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.