Created by @vylink in Q&As
@vylink
@vylink

Hi,

I'm using https://github.com/webrouse/n-sandbox/ with the nittro builder. I would like to change some default package's behaviour time to time. I would like to change the form validation way at this moment for example... I would like to show errors beside all form inputs when the form is submitted. I'm just not sure, how can I do if or if is that actually possible at all.

@jahudka
@jahudka

Hi, of course you can do it :-) the core of Nittro is the DI container which wires up different parts of Nittro to do their magic. You can easily implement your own version of any service, or just extend it and overload some of its methods, and then override the appropriate service in the Container.

For example, form validation is triggered from the Nittro.Forms.Form class' validate() method. This method calls netteForms.js's Nette.validateForm(). The netteForms.js validator calls the Nette.addError() method when a field's validation fails. This method is overridden by Nittro's formLocator service, implemented by the Nittro.Forms.Locator class; the class forwards errors from the netteForms.js implementation to the appropriate Nittro.Forms.Form instance which takes care of displaying the error via an error renderer.

The Nette.validateForm() method returns early, i.e. it returns false as soon as it encounters the first invalid field (at least if I'm reading the code correctly). So I think that to solve the specific issue you're describing you'd need to fix the netteForms.js side of things. But for the sake of the example, here's a couple of ways you can override / extend some of the functionality described above:

  1. It's Javascript, which means you can mostly do just whatever - including directly overriding a class's method. So if you want to implement your own custom validation, you can just do this:
    _context.invoke(function(Form) {
        Form.prototype.validate = function() {
            // do whatever you want and return a boolean
        };
    }, { Form: 'Nittro.Forms.Form' });

    If you include a script like this in the libraries.js section of your Nittro builder config, you're done - all your forms will now use your own custom method to validate on submit.

  2. You can extend the Form class just as you would in PHP. In this case you'd also need to extend the Locator class, because it's responsible for creating and managing instances of the Form class and since it's written by an idiot, there's no easier way to tell it to use a different Form implementation. Then you'll need to tell the DI container about your new Locator implementation.

    // assets/forms/myform.js
    _context.invoke('App.Forms', function() {
        var MyForm = _context.extend('Nittro.Forms.Form', function (form) {
            MyForm.Super.call(this, form); // call parent constructor
        }, {
            validate: function(sender) {
                // call the parent method if you need to:
                MyForm.Super.prototype.validate.call(this, sender);
                // or do whatever
            }
        })
    
        _context.register(MyForm, 'MyForm');
    });
    // assets/forms/mylocator.js
    _context.invoke('App.Forms', function(MyForm) {
        var MyLocator = _context.extend('Nittro.Forms.Locator', function (formErrorRenderer) {
            MyLocator.Super.call(this, formErrorRenderer);
        }, {
            getForm: function(id) {
                var elem;
    
                if (typeof id !== 'string') {
                    elem = id;
    
                    if (!elem.getAttribute('id')) {
                        elem.setAttribute('id', 'frm-anonymous' + (++anonId));
                    }
    
                    id = elem.getAttribute('id');
    
                }
    
                if (!(id in this._.registry)) {
                    // this is the line we need all this for:
                    this._.registry[id] = new MyForm(elem || id);
                    this._.registry[id].setErrorRenderer(this._.errorRenderer);
                    this.trigger('form-added', { form: this._.registry[id] });
                }
    
                return this._.registry[id];
            }
        });
    
        _context.register(MyLocator, 'MyLocator');
    });

    And finally in the Nittro builder configuration you'd register your new formLocator implementation in services:

    const builder = new nittro.Builder({
        // ...
        bootstrap: {
            services: {
                formLocator: 'App.Forms.MyLocator()'
            }
        }
    });

Sign in to post a reply