rivets-error-binder.js 4.24 KB
define(function(require) {
    'use strict';
    var module = require('module');
    var rivets = require('rivets');
    var _ = require('underscore');
    var Backbone = require('backbone');
    var $ = require('jquery');

    var rivetsBinderCall = function(binding, binderName, methodName, args) {
        var binder = rivets.binders[binderName];
        var method = binder[methodName];
        if (_.isFunction(binder)) {
            if (methodName === 'routine') {
                binder.apply(binding, args);
            }
        } else {
            binder[methodName].apply(binding, args);
        }
    };

    var render = function(el, cmd, errorList) {
        var viewClassPath = module.config().viewClassPath;
        if (viewClassPath) {
            var $el = $(el);
            var view = $el.data('error-view');
            if (view === undefined && cmd === 'bind') {
                $el.data('error-view', null);
                require([viewClassPath], function(ViewClass) {
                    $el.data('error-view', new ViewClass({el: el}));
                });
                return;
            }
            if (view) {
                view.errorCallback(cmd, errorList);
            }
            return;
        }
        var renderImpl = module.config().render;
        if (renderImpl) {
            return renderImpl.apply(this, arguments);
        }
    };

    function createTriggerProxy(obj, eventName) {
        return function() {
            obj.trigger.apply(obj, [eventName].concat(_.toArray(arguments)));
        };
    }

    function ErrorHandler(binding, el) {
        var seen = {};
        var observer = binding.observer;
        var oldParent;
        var parentObserver;
        var keyPath = observer.key.path;
        var parentPath = _.collect(observer.tokens, function(token) {
            return token.i + token.path;
        }).join('');
        this.on('bind', function() {
            render(el, 'bind');
            parentObserver = binding.observe(binding.view.models, parentPath, createTriggerProxy(this, 'parent-change'));
        }, this);
        this.on('focus', function() {
            render(el, 'focus', false);
        });
        this.on('blur', function() {
            seen[keyPath] = true;
            render(el, 'blur', false);
            if (observer.target) {
                observer.target.validate();
            }
        });
        this.on('validated', function(isValid, model, errors) {
            var errorList = errors[keyPath];
            if (errorList && seen[keyPath]) {
                render(el, 'validated', errorList);
            } else {
                render(el, 'validated', false);
            }
        });
        this.on('unbind', function() {
            render(el, 'unbind');
            this.trigger('stop-monitoring-parent');
            parentObserver.unobserve();
            parentObserver = null;
            $(el).off('.rivets-error-binder');
        });
        this.on('unbind parent-change', function() {
            if (oldParent) {
                this.stopListening(oldParent);
            }
            oldParent = null;
        }, this);
        this.on('bind parent-change', function() {
            var newParent = parentObserver.value();
            if (newParent) {
                seen = {};
                render(el, 'validated', false);
                this.listenTo(newParent, 'validated', createTriggerProxy(this, 'validated'));
            }
            oldParent = newParent;
        }, this);
        var $el = $(el);
        $el.on('focus.rivets-error-binder', createTriggerProxy(this, 'focus'));
        $el.on('blur.rivets-error-binder', createTriggerProxy(this, 'blur'));
    }
    _.extend(ErrorHandler.prototype, Backbone.Events);

    rivets.binders['error-*'] = {
        bind: function(el) {
            this.errorHandler = new ErrorHandler(this, el);
            this.errorHandler.trigger('bind');
            rivetsBinderCall(this, this.args[0], 'bind', arguments);
        },
        unbind: function(el) {
            this.errorHandler.trigger('unbind');
            delete this.errorHandler;
            rivetsBinderCall(this, this.args[0], 'unbind', arguments);
        },
        routine: function() {
            rivetsBinderCall(this, this.args[0], 'routine', arguments);
        }
    };
});