ncbo/bioportal_web_ui

View on GitHub
public/browse/lib/angular-bindonce/bindonce.js

Summary

Maintainability
D
1 day
Test Coverage
(function () {
    "use strict";
    /**
     * Bindonce - Zero watches binding for AngularJs
     * @version v0.3.3
     * @link https://github.com/Pasvaz/bindonce
     * @author Pasquale Vazzana <pasqualevazzana@gmail.com>
     * @license MIT License, http://www.opensource.org/licenses/MIT
     */

    var bindonceModule = angular.module('pasvaz.bindonce', []);

    bindonceModule.directive('bindonce', function ()
    {
        var toBoolean = function (value)
        {
            if (value && value.length !== 0)
            {
                var v = angular.lowercase("" + value);
                value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]');
            }
            else
            {
                value = false;
            }
            return value;
        };

        var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
        if (isNaN(msie))
        {
            msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
        }

        var bindonceDirective =
        {
            restrict: "AM",
            controller: ['$scope', '$element', '$attrs', '$interpolate', function ($scope, $element, $attrs, $interpolate)
            {
                var showHideBinder = function (elm, attr, value)
                {
                    var show = (attr === 'show') ? '' : 'none';
                    var hide = (attr === 'hide') ? '' : 'none';
                    elm.css('display', toBoolean(value) ? show : hide);
                };
                var classBinder = function (elm, value)
                {
                    if (angular.isObject(value) && !angular.isArray(value))
                    {
                        var results = [];
                        angular.forEach(value, function (value, index)
                        {
                            if (value) results.push(index);
                        });
                        value = results;
                    }
                    if (value)
                    {
                        elm.addClass(angular.isArray(value) ? value.join(' ') : value);
                    }
                };
                var transclude = function (transcluder, scope)
                {
                    transcluder.transclude(scope, function (clone)
                    {
                        var parent = transcluder.element.parent();
                        var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1];
                        var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
                        var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
                        angular.forEach(clone, function (node)
                        {
                            parentNode.insertBefore(node, afterNextSibling);
                        });
                    });
                };

                var ctrl =
                {
                    watcherRemover: undefined,
                    binders: [],
                    group: $attrs.boName,
                    element: $element,
                    ran: false,

                    addBinder: function (binder)
                    {
                        this.binders.push(binder);

                        // In case of late binding (when using the directive bo-name/bo-parent)
                        // it happens only when you use nested bindonce, if the bo-children
                        // are not dom children the linking can follow another order
                        if (this.ran)
                        {
                            this.runBinders();
                        }
                    },

                    setupWatcher: function (bindonceValue)
                    {
                        var that = this;
                        this.watcherRemover = $scope.$watch(bindonceValue, function (newValue)
                        {
                            if (newValue === undefined) return;
                            that.removeWatcher();
                            that.checkBindonce(newValue);
                        }, true);
                    },

                    checkBindonce: function (value)
                    {
                        var that = this, promise = (value.$promise) ? value.$promise.then : value.then;
                        // since Angular 1.2 promises are no longer
                        // undefined until they don't get resolved
                        if (typeof promise === 'function')
                        {
                            promise(function ()
                            {
                                that.runBinders();
                            });
                        }
                        else
                        {
                            that.runBinders();
                        }
                    },

                    removeWatcher: function ()
                    {
                        if (this.watcherRemover !== undefined)
                        {
                            this.watcherRemover();
                            this.watcherRemover = undefined;
                        }
                    },

                    runBinders: function ()
                    {
                        while (this.binders.length > 0)
                        {
                            var binder = this.binders.shift();
                            if (this.group && this.group != binder.group) continue;
                            var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value);
                            switch (binder.attr)
                            {
                                case 'boIf':
                                    if (toBoolean(value))
                                    {
                                        transclude(binder, binder.scope.$new());
                                    }
                                    break;
                                case 'boSwitch':
                                    var selectedTranscludes, switchCtrl = binder.controller[0];
                                    if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?']))
                                    {
                                        binder.scope.$eval(binder.attrs.change);
                                        angular.forEach(selectedTranscludes, function (selectedTransclude)
                                        {
                                            transclude(selectedTransclude, binder.scope.$new());
                                        });
                                    }
                                    break;
                                case 'boSwitchWhen':
                                    var ctrl = binder.controller[0];
                                    ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []);
                                    ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element });
                                    break;
                                case 'boSwitchDefault':
                                    var ctrl = binder.controller[0];
                                    ctrl.cases['?'] = (ctrl.cases['?'] || []);
                                    ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element });
                                    break;
                                case 'hide':
                                case 'show':
                                    showHideBinder(binder.element, binder.attr, value);
                                    break;
                                case 'class':
                                    classBinder(binder.element, value);
                                    break;
                                case 'text':
                                    binder.element.text(value);
                                    break;
                                case 'html':
                                    binder.element.html(value);
                                    break;
                                case 'style':
                                    binder.element.css(value);
                                    break;
                                case 'disabled':
                                    binder.element.prop('disabled', value);
                                    break;
                                case 'src':
                                    binder.element.attr(binder.attr, value);
                                    if (msie) binder.element.prop('src', value);
                                    break;
                                case 'attr':
                                    angular.forEach(binder.attrs, function (attrValue, attrKey)
                                    {
                                        var newAttr, newValue;
                                        if (attrKey.match(/^boAttr./) && binder.attrs[attrKey])
                                        {
                                            newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
                                            newValue = binder.scope.$eval(binder.attrs[attrKey]);
                                            binder.element.attr(newAttr, newValue);
                                        }
                                    });
                                    break;
                                case 'href':
                                case 'alt':
                                case 'title':
                                case 'id':
                                case 'value':
                                    binder.element.attr(binder.attr, value);
                                    break;
                            }
                        }
                        this.ran = true;
                    }
                };

                angular.extend(this, ctrl);
            }],

            link: function (scope, elm, attrs, bindonceController)
            {
                var value = attrs.bindonce && scope.$eval(attrs.bindonce);
                if (value !== undefined)
                {
                    bindonceController.checkBindonce(value);
                }
                else
                {
                    bindonceController.setupWatcher(attrs.bindonce);
                    elm.bind("$destroy", bindonceController.removeWatcher);
                }
            }
        };

        return bindonceDirective;
    });

    angular.forEach(
    [
        { directiveName: 'boShow', attribute: 'show' },
        { directiveName: 'boHide', attribute: 'hide' },
        { directiveName: 'boClass', attribute: 'class' },
        { directiveName: 'boText', attribute: 'text' },
        { directiveName: 'boBind', attribute: 'text' },
        { directiveName: 'boHtml', attribute: 'html' },
        { directiveName: 'boSrcI', attribute: 'src', interpolate: true },
        { directiveName: 'boSrc', attribute: 'src' },
        { directiveName: 'boHrefI', attribute: 'href', interpolate: true },
        { directiveName: 'boHref', attribute: 'href' },
        { directiveName: 'boAlt', attribute: 'alt' },
        { directiveName: 'boTitle', attribute: 'title' },
        { directiveName: 'boId', attribute: 'id' },
        { directiveName: 'boStyle', attribute: 'style' },
        { directiveName: 'boDisabled', attribute: 'disabled' },
        { directiveName: 'boValue', attribute: 'value' },
        { directiveName: 'boAttr', attribute: 'attr' },

        { directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 },
        { directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } },
        { directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch' },
        { directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch' }
    ],
    function (boDirective)
    {
        var childPriority = 200;
        return bindonceModule.directive(boDirective.directiveName, function ()
        {
            var bindonceDirective =
            {
                priority: boDirective.priority || childPriority,
                transclude: boDirective.transclude || false,
                terminal: boDirective.terminal || false,
                require: ['^bindonce'].concat(boDirective.require || []),
                controller: boDirective.controller,
                compile: function (tElement, tAttrs, transclude)
                {
                    return function (scope, elm, attrs, controllers)
                    {
                        var bindonceController = controllers[0];
                        var name = attrs.boParent;
                        if (name && bindonceController.group !== name)
                        {
                            var element = bindonceController.element.parent();
                            bindonceController = undefined;
                            var parentValue;

                            while (element[0].nodeType !== 9 && element.length)
                            {
                                if ((parentValue = element.data('$bindonceController'))
                                    && parentValue.group === name)
                                {
                                    bindonceController = parentValue;
                                    break;
                                }
                                element = element.parent();
                            }
                            if (!bindonceController)
                            {
                                throw new Error("No bindonce controller: " + name);
                            }
                        }

                        bindonceController.addBinder(
                        {
                            element: elm,
                            attr: boDirective.attribute || boDirective.directiveName,
                            attrs: attrs,
                            value: attrs[boDirective.directiveName],
                            interpolate: boDirective.interpolate,
                            group: name,
                            transclude: transclude,
                            controller: controllers.slice(1),
                            scope: scope
                        });
                    };
                }
            };

            return bindonceDirective;
        });
    })
})();