Giter Club home page Giter Club logo

Comments (34)

poteto avatar poteto commented on August 17, 2024 4

I'm not a fan of the service idea in the addon itself, it would require a lot of rework since most of the addon are vanilla JS modules with no knowledge of Ember. Instead, you can use computed properties on an Ember.Object:

const Messages = Ember.Object.extend({
  intl: Ember.inject.service(),

  inclusion: computed('intl', function() {
    // ...
  })
});

export default Messages.create();

from ember-changeset-validations.

jmacqueen avatar jmacqueen commented on August 17, 2024 4

Here's what I ended up doing, in case anyone else ends up here looking for help: https://gist.github.com/jmacqueen/6d31aaa5ba5f5841d123d0406f1f86c5

from ember-changeset-validations.

vladaspasic avatar vladaspasic commented on August 17, 2024 3

Hi all,

I have just forked this repo in order to implement this feature. @jmacqueen gave a good example on how to connect the Ember IoC with the messages.

Following his advice, I created an instance initializer that would pass the Application instance to the get-messages.js file in order to lookup them up via Ember container. Using the container there is no need to iterate through all defined modules and addons in order to find the custom validations/messages.js file.

You can check the fork here

To use this feature, you would simply need to create an instance-initializer in your application and define it like so:

export {default} from 'ember-changeset-validations/instance-initializers/ember-changeset-validations';

Then in your validations/messages.js file, you can define a property or implement the messageForKey function to load a message from either i18n, intl or any Ember Service:

import Ember from 'ember';
const { get, computed } = Ember;

export default Ember.Object.extend({
  i18n: Ember.inject.service(),

  i18nName: computed('i18n.locale', function () {
    return this.t('validations.i18nName');
  }),

  messageForKey(key) {
    const i18n = this.get('i18n');
    key = `validations.${key}`;

    if (i18n.exists(key)) {
      return i18n.t(key).toString();
    }
  }
});

If you are ok with this approach, I would gladly make a PR to incorporate these changes.

While working on this, I realised that that a simple API could be exposed in order to load custom messages and field descriptions.
From the current issues it seems that there is a need for further customisation. It would be great if we could make this library be even more flexible with validation messages.

My suggestions would be to expose messageForKey or descriptionForKey functions. Message and description lookup would look like this:

Ember.Mixin.create({
  // default messages
  defaults: {},

  getDescriptionFor(key = '') {
    let value;

    // Check if there is a custom lookup function defined
    if (canInvoke(this, 'descriptionForKey')) {
      value = this.descriptionForKey(key);
    }
    // Generate default description
    if (isNone(value)) {
      value = capitalize(dasherize(key).split(/[_-]/g).join(' '));
    }
    return value;
  },

  // use the unknown property hook in Ember.Object in order to find
  // messages which were not defined directly on the object
  unknownProperty(key = '') {
    let value;

    // Check if there is a custom lookup function defined
    if (canInvoke(this, 'messageForKey')) {
      value = this.messageForKey(key);
    }
    // Play nice with ObjectProxy instances
    if (isNone(value)) {
      value = this._super(key);
    }
    // Load default message
    if (isNone(value)) {
      value = get(this, `defaults.${key}`);
    }

    return value;
  }
})

As this change would be a rather big one, I would be good to agree on this beforehand :)

from ember-changeset-validations.

nandorstanko avatar nandorstanko commented on August 17, 2024 3

@martndemus @poteto Could you suggest some solution for this issue, guys? I'm using ember-i18n service for my translations, and I've tried the option #94 (comment) , but this doesn't work anymore. Any other elegant suggestion? Thanks for your great work btw!

from ember-changeset-validations.

makepanic avatar makepanic commented on August 17, 2024 2

Thanks,
Using CPs inside the messages object works without problems.
(Except that i have to manually inject the intl service using a container)

Another question:
Would it make sense to allow users to pass their own buildMessage function to move string template interpolation away from the validation-errors module to a pluggable part of the app (primarily i18n related libs)?
That way one isn't coupled to the string format ember-changeset-validations is using.

It could be solved by allowing the user to pass validatorOptions to the lookupValidator method: lookupValidator(Validator, {buildMessage: () => 'foo'}) which in turn will pass the validatorOptions into each validator.

Something like this: makepanic@8cca573

from ember-changeset-validations.

 avatar commented on August 17, 2024 2

what you can do is to register a seperate lookup util.

app/utils/lookup-validator.js

import originalLookupValidator from 'ember-changeset-validations';
import { merge } from '@ember/polyfills';
import { isArray } from '@ember/array';

export default function lookupValidator(validationMap = {}, options = {}) {
  return ({ key, newValue, oldValue, changes, content }) => {
    let validationResult = originalLookupValidator(validationMap)({ key, newValue, oldValue, changes, content });

    if (options.intl && options.intl.t && isArray(validationResult)) {
      validationResult = validationResult.map((v) => {
        if (v.message && v.attrs) {
          return options.intl.t(v.message, merge(v.attrs, { key: options.intl.t(`validations.changeset.keys.${key}`) }));
        }

        return v;
      })
    }

    return validationResult;
  }
}

app/validations/messages.js

export default {
  inclusion: 'validations.changeset.messages.inclusion',
  exclusion: 'validations.changeset.messages.exclusion',
  invalid: 'validations.changeset.messages.invalid',
  confirmation: "validations.changeset.messages.confirmation",
  accepted: 'validations.changeset.messages.accepted',
  empty: "validations.changeset.messages.empty",
  blank: 'validations.changeset.messages.blank',
  present: "validations.changeset.messages.present",
  collection: 'validations.changeset.messages.collection',
  singular: "validations.changeset.messages.singular",
  tooLong: 'validations.changeset.messages.tooLong',
  tooShort: 'validations.changeset.messages.tooShort',
  between: 'validations.changeset.messages.between',
  before: 'validations.changeset.messages.before',
  onOrBefore: 'validations.changeset.messages.onOrBefore',
  after: 'validations.changeset.messages.after',
  onOrAfter: 'validations.changeset.messages.onOrAfter',
  wrongDateFormat: 'validations.changeset.messages.wrongDateFormat',
  wrongLength: 'validations.changeset.messages.wrongLength',
  notANumber: 'validations.changeset.messages.notANumber',
  notAnInteger: 'validations.changeset.messages.notAnInteger',
  greaterThan: 'validations.changeset.messages.greaterThan',
  greaterThanOrEqualTo: 'validations.changeset.messages.greaterThanOrEqualTo',
  equalTo: 'validations.changeset.messages.equalTo',
  lessThan: 'validations.changeset.messages.lessThan',
  lessThanOrEqualTo: 'validations.changeset.messages.lessThanOrEqualTo',
  otherThan: 'validations.changeset.messages.otherThan',
  odd: 'validations.changeset.messages.odd',
  even: 'validations.changeset.messages.even',
  positive: 'validations.changeset.messages.positive',
  multipleOf: 'validations.changeset.messages.multipleOf',
  date: 'validations.changeset.messages.date',
  email: 'validations.changeset.messages.email',
  phone: 'validations.changeset.messages.phone',
  url: 'validations.changeset.messages.url',

  formatMessage: (message, attrs) => {
    return { message, attrs };
  }
}

/translations/de-de.yml

##############################
# VALIDATIONS
##############################
validations:
  changeset:
    messages:
      inclusion: '{key} is not included in the list'
      exclusion: '{key} is reserved'
      invalid: '{key} is invalid'
      confirmation: "{key} doesn't match {on}"
      accepted: '{key} must be accepted'
      empty: "{key} can't be empty"
      blank: '{key} must be blank'
      present: "{key} can't be blank"
      collection: '{key} must be a collection'
      singular: "{key} can't be a collection"
      tooLong: '{key} is too long (maximum is {max} characters)'
      tooShort: '{key} is too kurz (minimum is {min} characters)'
      between: '{key} must be between {min} and {max} characters'
      before: '{key} must be before {before}'
      onOrBefore: '{key} must be on or before {onOrBefore}'
      after: '{key} must be after {after}'
      onOrAfter: '{key} must be on or after {onOrAfter}'
      wrongDateFormat: '{key} must be in the format of {format}'
      wrongLength: '{key} is the wrong length (should be {is} characters)'
      notANumber: '{key} must be a number'
      notAnInteger: '{key} must be an integer'
      greaterThan: '{key} must be greater than {gt}'
      greaterThanOrEqualTo: '{key} must be greater than or equal to {gte}'
      equalTo: '{key} must be equal to {is}'
      lessThan: '{key} must be less than {lt}'
      lessThanOrEqualTo: '{key} must be less than or equal to {lte}'
      otherThan: '{key} must be other than {value}'
      odd: '{key} must be odd'
      even: '{key} must be even'
      positive: '{key} must be positive'
      multipleOf: '{key} must be a multiple of {multipleOf}'
      date: '{key} must be a valid date'
      email: '{key} ist keine valide email'
      phone: '{key} must be a valid phone number'
      url: '{key} must be a valid url'
    keys:
      email: E-Mail
      password: Passwort

and finally use it....

...
import lookupValidator from '../../../utils/lookup-validator';
...
return new Changeset({}, lookupValidator(UserValidations, {intl: this.intl}), UserValidations);

this way no other changes are required and it works out of the box with ember-form-for for example....

little hacky yes but this works as it should with every validator and its also possible to change locales at runtime. Hope this helps

from ember-changeset-validations.

xcambar avatar xcambar commented on August 17, 2024 2

Hi,
I've come up with a PR that allows to postpone i18n/intl work outside of ember-changeset-validations by making it return (optionally) an object instead of a String. It works wonders for us: #185

from ember-changeset-validations.

karellm avatar karellm commented on August 17, 2024 1

I was wondering if this addon could check whether message.js export a POJO or an Ember.Object. If it is an Ember object, it could take care of the creation and owner injection:

import Ember from 'ember';
const { computed } = Ember;

export default Ember.Object.extend({
  i18n: Ember.inject.service(),

  i18nName: computed('i18n.locale', function () {
    return this.t('validations.i18nName');
  })
});

// I do not create or export the object 

And the addons would be responsible to:

import Messages from 'validations/messages.js';

Messages.create(
  Ember.getOwner(this).ownerInjection()
);

Does it sound like it would work? And is it something you'd be willing to implement? cc @poteto @martndemus

from ember-changeset-validations.

sebastianhelbig avatar sebastianhelbig commented on August 17, 2024 1

Does the solution #94 (comment) still work? It seems computed properties aren't merged by Ember.merge in https://github.com/DockYard/ember-changeset-validations/blob/c638e81a537c5e9fb427227cc0e35852d01a6919/addon/utils/with-defaults.js#L13-L15

from ember-changeset-validations.

bgentry avatar bgentry commented on August 17, 2024 1

Yesterday, I finally ran into the need to use a service in my validations. In my particular case, I needed to fetch a dynamic array of strings from my API to validate inclusion against. After reading this thread, I almost tried to make this work in this addon.

However, I realized that I would essentially be rebuilding a bunch of the stuff in ember-changeset-cp-validations. I decided to just start using that addon instead. I think that model is a lot more robust and fits better with any scenario where you need to access dynamic data provided by an Ember service. It was a pretty simple transition and I will probably move my other validations over if the need arises. Definitely recommended!

from ember-changeset-validations.

KamiKillertO avatar KamiKillertO commented on August 17, 2024 1

I've tried different approaches and addon to display/translate validations messages at runtime and finally I found an easy way to do it. I've followed https://github.com/poteto/ember-changeset-validations#overriding-validation-messages but instead of returning a real message I return a valid translation key for ember-i18n.

// app/validations/messages.js
export default {
  inclusion: 'errors.validations.inclusion',
  exclusion: 'errors.validations.invalid',
  invalid: 'errors.validations.invalid',
  // ...
}

And now I my view I can use this key to translate validation message at runtime using the t helper.
Like this:

{{#if changeset.isInvalid}}
  <p>There were errors in your form:</p>
  <ul>
    {{#each changeset.errors as |error|}}
      <li>{{error.key}}: {{t error.validation}}</li>
    {{/each}}
  </ul>
{{/if}}

from ember-changeset-validations.

gauravjain028 avatar gauravjain028 commented on August 17, 2024 1

@xcambar I have setup it on https://github.com/gauravjain028/ember-intl-changeset-error-handling
Template File : https://github.com/gauravjain028/ember-intl-changeset-error-handling/blob/master/app/templates/application.hbs
When in load the app, it give me js error
The intl string context variable 'description' was not provided to the string '{description} can't be blank'

from ember-changeset-validations.

 avatar commented on August 17, 2024

@makepanic Are you using ember-i18n or equivalent?

If so, I could write a patch that tries to lookup the messages from an i18n service at run-time, that should account for a locale change.

from ember-changeset-validations.

makepanic avatar makepanic commented on August 17, 2024

Yes i'm using ember-intl.

A configurable service sounds like a great idea.

from ember-changeset-validations.

 avatar commented on August 17, 2024

That would work.

from ember-changeset-validations.

jmacqueen avatar jmacqueen commented on August 17, 2024

@makepanic How did you get this to work? I can't seem to sort out the manual injection of the (I'm using ember-i18n) service using container. Do you have a link to your implementation?

from ember-changeset-validations.

makepanic avatar makepanic commented on August 17, 2024

@jmacqueen i went with passing a buildMessage option to the lookupValidator method (see makepanic@8cca573).
Message building happens by calling the provided buildMessage function which in turn calls ember-intl to translate the given incoming validation message key.

from ember-changeset-validations.

celicoo avatar celicoo commented on August 17, 2024

Hei @jmacqueen, i'm seeing this error in my console:

Uncaught TypeError: _siteUtilsApplication.default.instance.lookup is not a function

Any ideias why?

Thank you! <3

from ember-changeset-validations.

poteto avatar poteto commented on August 17, 2024

@karellm I would be open to this. Do you have time to open a PR?

from ember-changeset-validations.

karellm avatar karellm commented on August 17, 2024

@poteto I tried but I'm not very familiar with Ember internals and add-ons development. this is actually undefined inside the utils. It looks like it should be passed by buildMessage which makes the API a little weird.

Also I'm not sure how to go about testing this since the resolver looks for validations/messages so I can't have 2 different files (one POJO, one Ember.Object).

I'm happy to do it but I would really appreciate some pointers. Thanks!

from ember-changeset-validations.

CmdrChaos avatar CmdrChaos commented on August 17, 2024

@poteto, @karellm
Any progress on this? I find myself stuck trying to get an Ember.Object to be recognized from messages.js. As long as it's a simple POJO - no issues, but when wrapped in an Ember.Object, the add-on reverts to its defaults rather than using the provided object from messages.js.

from ember-changeset-validations.

hornetnz avatar hornetnz commented on August 17, 2024

We implemented the changes @jmacqueen did, and the breakpoint on the initialize method in instance-initializers/application.js is hitting. However nothing is hitting for our validations/messages.js file. Is there something that needs to be added to our validators file to get our translated messages file loaded?

A sample of our validator file...

    import {
    validateLength,
    validateFormat
} from 'ember-changeset-validations/validators';
import { validatePhone, validateExtension } from 'ember-engine-contacts/validators/phone';
import { regularExpressions } from 'ember-validators/format';
import validateTwitter from 'ember-engine-contacts/validators/twitter';

export default {
    workEmail: [
        validateLength({ max: 254, allowNone: true, allowBlank: true }),
        validateFormat({ type: 'email', allowNone: true, allowBlank: true, regex: regularExpressions.email })
    ]
};

I also tried removing any code we had in both app/validations/messages and addon/validations/messages with

export default {
    email: 'FOOBAR'
}

as the doc states to do, but still the default message showed.

I can add 'message' to the object parameters in the validateFormat method, which works...but not sure how to get our overridden messages file to work.
Any help would be greatly appreciated!

from ember-changeset-validations.

0xadada avatar 0xadada commented on August 17, 2024

@makepanic @martndemus @karellm @nandorstanko I've released an Ember Addon to apply ember-i18n translations to these messages in a beta version here https://github.com/mirai-audio/ember-i18n-changeset-validations. Could use a few hands to try it before I release a v1.0.0

Edit.
I release v1.0.0

from ember-changeset-validations.

nandorstanko avatar nandorstanko commented on August 17, 2024

@0xadada Correct me if I'm wrong, but your solution doesn't support changing locale without reloading the app. That's a huge feature, that is a MUST HAVE for me... That is why I use ember-i18n addon in the first place. I've switched to ember-changeset-cp-validations+ember-i18n-cp-validations combo, and it's working for me. Probably slower this way, but not an issue so far.

from ember-changeset-validations.

0xadada avatar 0xadada commented on August 17, 2024

from ember-changeset-validations.

makepanic avatar makepanic commented on August 17, 2024

While working on a new project with intl + changeset-validations, I've found another workaround (hack) to get this working without touching too many things:

  • changeset-validations is retrieveing messages from ember-validators/messages
  • if one overwrites Messages.formatMessage, one can provide a custom translation solution:
    • configure validations/messages to be a simple identity map where each key is its value. This causes formatMessage to be called with the error code (i.e. inclusion).
export default {
  inclusion: 'inclusion',
  exclusion: 'exclusion',
  ...
  url: 'url'
}
  • change Message.formatMessage to take the error code "message" and context and pass it into intl:
    Messages.formatMessage = (message, context = {}) => {
      let errorKey = message;

      if (isNone(errorKey) || typeof errorKey !== 'string') {
        return intl.t('validations.invalid');
      }
      return intl.t(`validations.${errorKey}`, context);
    }

Now if changeset-validations tries to format the translated template, it passes the error code and get's the translation using whatever you configured.

In ember-intl context one can easily add these error key translations.

validations:
  inclusion: 'Ist nicht in der Liste enthalten'
  ...

I've added overwriting formatMessage into the intl service init hook and it looks like it's working fine.

from ember-changeset-validations.

scottkidder avatar scottkidder commented on August 17, 2024

I can confirm that @xcambar's solution is very flexible and works great for my use case.

from ember-changeset-validations.

xcambar avatar xcambar commented on August 17, 2024

FYI, #185 has been merged.

See the PR and this setion of the docs for info.

Should this issue be closed now?

from ember-changeset-validations.

makepanic avatar makepanic commented on August 17, 2024

Looks good. Can we maybe add an example on how to use this with intl/i18n?

I'm ok with closing this

from ember-changeset-validations.

xcambar avatar xcambar commented on August 17, 2024

I'm using it with something of the form (wrapped in a helper):

{{t (concat "path.to.errors." error.type) errors.context}}

from ember-changeset-validations.

scottkidder avatar scottkidder commented on August 17, 2024

I have kind of tackled this and our migration from ember-i18n to ember-intl at the same time. The one gotcha I ran into was that the {{t }} helper from ember-i18n allowed passing a hash of i18n paramaters, where the one from ember-intl requires passing each as it's own parameter, which isn't ideal when we don't know the parameters upfront.

from ember-changeset-validations.

gauravjain028 avatar gauravjain028 commented on August 17, 2024

@xcambar passing errors.context with name attribute is not working on my end ? Can you help with that ?

from ember-changeset-validations.

xcambar avatar xcambar commented on August 17, 2024

I can try :)
In order to ease investigation, can you share a twiddle or a repo that reproduces the behaviour, please?

from ember-changeset-validations.

k-dauda avatar k-dauda commented on August 17, 2024

I was having issues in octane but I was able to get a good workaround with custom validators.

import Component from '@glimmer/component';
import Changeset from 'ember-changeset';
import lookupValidator from 'ember-changeset-validations';
import  { inject as service } from '@ember/service';

export default class MyComponent extends Component {
  @service intl;

  validator({ errorKey }) {
    let errorMsg = this.intl.t(`${errorKey}.error.msg`);

    return function(_, value) {
      let isInValid = ....; // custom validation logic

      if (isInValid) {
        return errorMsg;
      }

      return true;
    }
  }

  validators = {
    name: this.validator({ errorKey: 'name.field' }),
    email: this.validator({ errorKey: 'email.field' })
  };

  changeset = new Changeset(
    { 
      name: this.name,
      email: this.email
    },
    lookupValidator(this.validators),
    this.validators
  );
}

from ember-changeset-validations.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.