<commit_before>/**
 * Add fancy ui modifications - the visual stuff - here.
 * Respond functionality like 'event':UI.myFunctionality(currentElement)
 *
 * Example:
 *      <button type="button" data-plenty="click:UI.addTooltip(this)">go to top</button>
 *
 */
(function( $, pm )
{
    pm.directive( 'UI', function( MediaSizeService, SocialShareService )
    {
        // elements to calculate height.
        var equalHeightElementList = [];
        var toTopButtonList        = [];

        return {
            initUIWindowEvents  : initUIWindowEvents,
            addContentPageSlider: addContentPageSlider,
            equalHeight         : equalHeight,
            initToTop           : initToTop,
            initLazyload        : initLazyload,
            initSlideToggle     : initSlideToggle,
            slideDown           : slideDown,
            slideUp             : slideUp,
            slideToggle         : slideToggle,
            toggleHideShow      : toggleHideShow,
            toggleSocialShare   : toggleSocialShare,
            toggleClass         : toggleClass,
            addClass            : addClass,
            removeClass         : removeClass
        };

        function initUIWindowEvents()
        {
            // resize elements on window size change.
            $( window ).on( 'sizeChange contentChanged', function()
            {
                fireEqualHeight();
            } );

            $( window ).on( "scroll resize", function()
            {
                if ( toTopButtonList.length > 0 )
                {
                    if ( $( document ).scrollTop() > 100 )
                    {
                        doToArrayElements( toTopButtonList, "addClass", "visible" );
                    }
                    else
                    {
                        doToArrayElements( toTopButtonList, "removeClass", "visible" );
                    }
                }
            } );
        }

        /**
         * Adds content page slider (owlCarousel)
         *
         * usage:
         *

         *

         *         ...
         *

         *

         *         ...
         *

         *     ...
         *

         *
         * Legacy directive selector: data-plenty="contentpageSlider"
         *
         * @param elem
         */
        function addContentPageSlider( elem )
        {
            $( elem ).owlCarousel( {
                navigation     : true,
                navigationText : false,
                slideSpeed     : 1000,
                paginationSpeed: 1000,
                singleItem     : true,
                autoPlay       : 6000,
                stopOnHover    : true,
                afterMove      : function( current )
                {
                    $( current ).find( 'img[data-plenty-rel="lazyload"]' ).trigger( 'appear' );
                }
            } );
        }

        /**
         * Equal Box height
         * Calculates equal box height for chosen elements.
         *
         * Legacy directive selector: data-plenty-equal
         *
         * @param elem
         * @param elementExists - default false
         */
        function equalHeight( elem, mediaSizes, elementExists )
        {
            var $elem            = $( elem );
            var maxHeight        = 0;
            var $equalTarget     = {};
            var $equalTargetList = $elem.find( '[data-plenty-rel="equal-target"]' ).length > 0 ? $elem.find( '[data-plenty-rel="equal-target"]' ) : $elem.children();

            // if element wasn't pushed before.
            if ( elementExists !== true )
            {
                equalHeightElementList.push( elem );
            }

            for ( var i = $equalTargetList.length; i >= 0; i-- )
            {
                $equalTarget = $( $equalTargetList[i] );
                $equalTarget.css( 'height', '' );

                if ( $equalTarget.outerHeight( true ) > maxHeight )
                {
                    maxHeight = $equalTarget.outerHeight( true );
                }
            }

            if ( !mediaSizes || MediaSizeService.isInterval( mediaSizes ) )
            {
                $equalTargetList.height( maxHeight );
            }
        }

        /**
         * Scroll page to top.
         * Just add without events.
         *
         * Legacy directive selector: data-plenty="toTop"
         *
         * @param elem
         */
        function initToTop( elem )
        {
            var $elem = $( elem );

            $elem.click( function()
            {
                $( 'html, body' ).animate( {
                    scrollTop: 0
                }, 400 );
                return false;
            } );

            if ( !!$.inArray( $elem, toTopButtonList ) )
            {
                toTopButtonList.push( $elem );
            }
        }

        /**
         * lazy load on ready.
         *
         * Legacy directive selector: img[data-plenty-lazyload]
         *
         * @param elem
         */
        function initLazyload( elem, effect )
        {
            var $elem = $( elem );

            $elem.lazyload( {
                effect: effect
            } );
            $elem.on( "loaded", function()
            {
                $elem.css( 'display', 'inline-block' );
            } );
        }

        /**
         * Toggle show and hide animation.
         *
         * Legacy directive selector: data-plenty="openCloseToggle"
         *
         * @param elem
         */
        function toggleHideShow( elem )
        {

            console.log( elem );

            var $elem       = $( elem );
            var $elemParent = $elem.parent();

            $elemParent.addClass( 'animating' );
            $elem.siblings( 'ul' ).slideToggle( 200, function()
            {
                if ( $elemParent.is( '.open' ) )
                {
                    $elemParent.removeClass( 'open' );
                }
                else
                {
                    $elemParent.addClass( 'open' );
                }
                $elem.siblings( 'ul' ).removeAttr( 'style' );
                $elemParent.removeClass( 'animating' );
            } );
        }

        /**
         * Toggle target content on click.
         * Bind to checked-/ unchecked-property of radio buttons
         *
         * Legacy directive selector: data-plenty-slidetoggle
         *
         * @param elem
         */
        function initSlideToggle( elem, checked )
        {
            var $elem          = $( elem );
            var $targetElement = $( $elem.attr( 'data-plenty-rel' ) );

            if ( $elem.is( 'input[type="radio"]' ) )
            {
                // is radio button
                var $radioGroupList  = $( 'input[type="radio"][name="' + ( $elem.attr( 'name' ) ) + '"]' );
                var visibleOnChecked = !checked || checked == 'checked';

                $radioGroupList.change( function()
                {
                    var $self = $( this );
                    $targetElement.parents( '[data-plenty-rel="equal-target"]' ).css( 'height', 'auto' );

                    if ( $self.is( ':checked' ) && $self[0] === $elem[0] && visibleOnChecked == true )
                    {
                        // checked
                        $targetElement.slideDown( 400, function()
                        {
                            fireEqualHeight();
                        } );
                    }
                    else
                    {
                        // unchecked (since other radio button has been checked)
                        $targetElement.slideUp( 400, function()
                        {
                            fireEqualHeight();
                        } );
                    }
                } );
            }
            else
            {
                // is not radio button
                $elem.click( function()
                {
                    //$targetElement.parents( '[data-plenty-rel="equal-target"]' ).css( 'height', 'auto' );

                    $elem.addClass( 'animating' );
                    $targetElement.slideToggle( 400, function()
                    {
                        $elem.removeClass( 'animating' );
                        $elem.toggleClass( 'active' );
                        fireEqualHeight();
                    } );
                } );
            }
        }

        function slideDown( target, duration )
        {
            slideAction( $( target ), duration, 'slideDown' );
        }

        function slideUp( target, duration )
        {
            slideAction( $( target ), duration, 'slideUp' );
        }

        function slideToggle( target, duration )
        {
            slideAction( $( target ), duration, 'slideToggle' );
        }

        function slideAction( $target, duration, callbackString )
        {
            duration = duration || 400;
            $target.parents( '[data-plenty-rel="equal-target"]' ).css( 'height', 'auto' );
            $target[callbackString]( duration, function()
            {
                fireEqualHeight();
            } );
        }

        /**
         * TODO check comment
         * Social Share Activation
         * Activate and load share-buttons manually by clicking a separate button
         * Usage / data-attributes:
         *

         *            Will be used to activate the service set in
         * data-plenty-social=""
         *       Will be replaced with loaded share button
         *

         *
         * possible values for data-plenty-social:
         * "facebook-like"            : Load Facebooks "Like"-Button
         * "facebook-recommend"        : Load Facebooks "Recommend"-Button
         * "twitter"                : Load Twitter Button
         * "google-plus"            : Load google "+1"-Button
         *
         * Additional Tooltips
         * You can extend the parent element with a (bootstrap) tooltip by adding data-toggle="tooltip" and
         * title="TOOLTIP CONTENT" Tooltip will be destroyed after activating a social service
         * (!) Requires bootstrap.js
         *
         * Legacy directive selector: data-plenty-social
         *
         * @param elem
         */
        function toggleSocialShare( elem, socialShareService )
        {
            var $elem   = $( elem );
            var $toggle = $elem.find( '[data-plenty-rel="social-switch"]' );

            // append container to put / delete service.html
            $elem.append( '

' );

            // add "off" class to switch, if neither "off" or "on" is set
            // replaced hasClass() with is() benchmark: http://jsperf.com/hasclasstest
            if ( !$toggle.is( 'off, on' ) )
            {
                $toggle.addClass( 'off' );
            }

            // toggle switch
            $toggle.on( 'click', function()
            {
                if ( $toggle.hasClass( 'off' ) )
                {
                    // TODO remove bootstrap dependency
                    if ( $elem.attr( "data-toggle" ) == "tooltip" )
                    {
                        $elem.tooltip( 'destroy' )
                    }
                    $toggle.removeClass( 'off' ).addClass( 'on' );
                    // hide dummy button
                    $elem.find( '[data-plenty-rel="social-placeholder"]' ).hide();
                    // load HTML defined in 'api'
                    $elem.find( '.social-container' ).append( SocialShareService.getSocialService( socialShareService ) );
                }
                // do not disable social medias after activation
            } );
        }

        /**
         * Toggle Class
         * toggle style-classes on click
         * Usage / data-attribute:
         *

         * target    :    jQuery selector to toggle the class at.
         * class        :  class(es) to toggle at target element
         * media        :  only toggle class on given media sizes (optional)
         *
         * (!) using data-plenty-toggle on -elements will prevent redirecting to href=""
         *
         * Legacy directive selector: data-plenty-toggle
         *
         * @param cssClass
         * @param target
         * @param interval
         */
        function toggleClass( cssClass, target, interval )
        {
            var $target = $( target );
            /* FIXME
             * Callisto 3.1 Design adaption:
             * NavigationCategoriesList
             * Line 8
             * BEFORE:
             *

             * AFTER:
             *

             *
             * Line 10
             * BEFORE:
             *
             * AFTER:
             *
             *
             * */
            if ( $target.parents( ".navbar-main" ).length > 0 )
            {
                var $elem = $target.children( "span" );
                pm.directives["MobileDropdown"].slideDropdown( $elem );
                return true;
            }

            if ( !!target && !!cssClass && ( !interval || MediaSizeService.isInterval( interval ) ) )
            {
                var e = pm.getRecentEvent();
                if ( !!e )
                {
                    e.preventDefault();
                }

                $target.toggleClass( cssClass );
                return false;
            }
        }

        function addClass( cssClass, target, interval )
        {
            if ( !!target && !!cssClass && ( !interval || MediaSizeService.isInterval( interval ) ) )
            {
                var e = pm.getRecentEvent();
                if ( !!e )
                {
                    e.preventDefault();
                }

                $( target ).addClass( cssClass );
                return false;
            }
        }

        function removeClass( cssClass, target, interval )
        {
            if ( !!target && !!cssClass && ( !interval || MediaSizeService.isInterval( interval ) ) )
            {
                var e = pm.getRecentEvent();
                if ( !!e )
                {
                    e.preventDefault();
                }

                $( target ).removeClass( cssClass );
                return false;
            }
        }

        /*
         ##### PRIVATE FUNCTIONS ######
         */

        function fireEqualHeight()
        {
            for ( var i = equalHeightElementList.length - 1; i >= 0; i-- )
            {
                equalHeight( equalHeightElementList[i], '', true );
            }
        }

        function doToArrayElements( array, func, params )
        {
            for ( var i = array.length - 1; i >= 0; i-- )
            {
                array[i][func]( params );
            }
        }

    }, ['MediaSizeService', 'SocialShareService'] );
}( jQuery, PlentyFramework ));<commit_msg>Enable Lazyload for Background-Images (also in OwlCarousel).<commit_after>/**
 * Add fancy ui modifications - the visual stuff - here.
 * Respond functionality like 'event':UI.myFunctionality(currentElement)
 *
 * Example:
 *      <button type="button" data-plenty="click:UI.addTooltip(this)">go to top</button>
 *
 */
(function( $, pm )
{
    pm.directive( 'UI', function( MediaSizeService, SocialShareService )
    {
        // elements to calculate height.
        var equalHeightElementList = [];
        var toTopButtonList        = [];

        return {
            initUIWindowEvents  : initUIWindowEvents,
            addContentPageSlider: addContentPageSlider,
            equalHeight         : equalHeight,
            initToTop           : initToTop,
            initLazyload        : initLazyload,
            initSlideToggle     : initSlideToggle,
            slideDown           : slideDown,
            slideUp             : slideUp,
            slideToggle         : slideToggle,
            toggleHideShow      : toggleHideShow,
            toggleSocialShare   : toggleSocialShare,
            toggleClass         : toggleClass,
            addClass            : addClass,
            removeClass         : removeClass
        };

        function initUIWindowEvents()
        {
            // resize elements on window size change.
            $( window ).on( 'sizeChange contentChanged', function()
            {
                fireEqualHeight();
            } );

            $( window ).on( "scroll resize", function()
            {
                if ( toTopButtonList.length > 0 )
                {
                    if ( $( document ).scrollTop() > 100 )
                    {
                        doToArrayElements( toTopButtonList, "addClass", "visible" );
                    }
                    else
                    {
                        doToArrayElements( toTopButtonList, "removeClass", "visible" );
                    }
                }
            } );
        }

        /**
         * Adds content page slider (owlCarousel)
         *
         * usage:
         *

         *

         *         ...
         *

         *

         *         ...
         *

         *     ...
         *

         *
         * Legacy directive selector: data-plenty="contentpageSlider"
         *
         * @param elem
         */
        function addContentPageSlider( elem )
        {
            $( elem ).owlCarousel( {
                navigation     : true,
                navigationText : false,
                slideSpeed     : 1000,
                paginationSpeed: 1000,
                singleItem     : true,
                autoPlay       : 6000,
                stopOnHover    : true,
                afterMove      : function( current )
                {
                    $( current ).find( '[data-plenty-rel="lazyload"]' ).trigger( 'appear' );
                }
            } );
        }

        /**
         * Equal Box height
         * Calculates equal box height for chosen elements.
         *
         * Legacy directive selector: data-plenty-equal
         *
         * @param elem
         * @param elementExists - default false
         */
        function equalHeight( elem, mediaSizes, elementExists )
        {
            var $elem            = $( elem );
            var maxHeight        = 0;
            var $equalTarget     = {};
            var $equalTargetList = $elem.find( '[data-plenty-rel="equal-target"]' ).length > 0 ? $elem.find( '[data-plenty-rel="equal-target"]' ) : $elem.children();

            // if element wasn't pushed before.
            if ( elementExists !== true )
            {
                equalHeightElementList.push( elem );
            }

            for ( var i = $equalTargetList.length; i >= 0; i-- )
            {
                $equalTarget = $( $equalTargetList[i] );
                $equalTarget.css( 'height', '' );

                if ( $equalTarget.outerHeight( true ) > maxHeight )
                {
                    maxHeight = $equalTarget.outerHeight( true );
                }
            }

            if ( !mediaSizes || MediaSizeService.isInterval( mediaSizes ) )
            {
                $equalTargetList.height( maxHeight );
            }
        }

        /**
         * Scroll page to top.
         * Just add without events.
         *
         * Legacy directive selector: data-plenty="toTop"
         *
         * @param elem
         */
        function initToTop( elem )
        {
            var $elem = $( elem );

            $elem.click( function()
            {
                $( 'html, body' ).animate( {
                    scrollTop: 0
                }, 400 );
                return false;
            } );

            if ( !!$.inArray( $elem, toTopButtonList ) )
            {
                toTopButtonList.push( $elem );
            }
        }

        /**
         * lazy load on ready.
         *
         * Legacy directive selector: img[data-plenty-lazyload]
         *
         * @param elem
         */
        function initLazyload( elem, effect )
        {
            var $elem = $( elem );

            $elem.lazyload( {
                effect: effect
            } );
            if( $elem.is('img') )
            {
                $elem.on( 'loaded', function()
                {
                    $elem.css( 'display', 'inline-block' );
                } );
            }
        }

        /**
         * Toggle show and hide animation.
         *
         * Legacy directive selector: data-plenty="openCloseToggle"
         *
         * @param elem
         */
        function toggleHideShow( elem )
        {

            console.log( elem );

            var $elem       = $( elem );
            var $elemParent = $elem.parent();

            $elemParent.addClass( 'animating' );
            $elem.siblings( 'ul' ).slideToggle( 200, function()
            {
                if ( $elemParent.is( '.open' ) )
                {
                    $elemParent.removeClass( 'open' );
                }
                else
                {
                    $elemParent.addClass( 'open' );
                }
                $elem.siblings( 'ul' ).removeAttr( 'style' );
                $elemParent.removeClass( 'animating' );
            } );
        }

        /**
         * Toggle target content on click.
         * Bind to checked-/ unchecked-property of radio buttons
         *
         * Legacy directive selector: data-plenty-slidetoggle
         *
         * @param elem
         */
        function initSlideToggle( elem, checked )
        {
            var $elem          = $( elem );
            var $targetElement = $( $elem.attr( 'data-plenty-rel' ) );

            if ( $elem.is( 'input[type="radio"]' ) )
            {
                // is radio button
                var $radioGroupList  = $( 'input[type="radio"][name="' + ( $elem.attr( 'name' ) ) + '"]' );
                var visibleOnChecked = !checked || checked == 'checked';

                $radioGroupList.change( function()
                {
                    var $self = $( this );
                    $targetElement.parents( '[data-plenty-rel="equal-target"]' ).css( 'height', 'auto' );

                    if ( $self.is( ':checked' ) && $self[0] === $elem[0] && visibleOnChecked == true )
                    {
                        // checked
                        $targetElement.slideDown( 400, function()
                        {
                            fireEqualHeight();
                        } );
                    }
                    else
                    {
                        // unchecked (since other radio button has been checked)
                        $targetElement.slideUp( 400, function()
                        {
                            fireEqualHeight();
                        } );
                    }
                } );
            }
            else
            {
                // is not radio button
                $elem.click( function()
                {
                    //$targetElement.parents( '[data-plenty-rel="equal-target"]' ).css( 'height', 'auto' );

                    $elem.addClass( 'animating' );
                    $targetElement.slideToggle( 400, function()
                    {
                        $elem.removeClass( 'animating' );
                        $elem.toggleClass( 'active' );
                        fireEqualHeight();
                    } );
                } );
            }
        }

        function slideDown( target, duration )
        {
            slideAction( $( target ), duration, 'slideDown' );
        }

        function slideUp( target, duration )
        {
            slideAction( $( target ), duration, 'slideUp' );
        }

        function slideToggle( target, duration )
        {
            slideAction( $( target ), duration, 'slideToggle' );
        }

        function slideAction( $target, duration, callbackString )
        {
            duration = duration || 400;
            $target.parents( '[data-plenty-rel="equal-target"]' ).css( 'height', 'auto' );
            $target[callbackString]( duration, function()
            {
                fireEqualHeight();
            } );
        }

        /**
         * TODO check comment
         * Social Share Activation
         * Activate and load share-buttons manually by clicking a separate button
         * Usage / data-attributes:
         *

         *            Will be used to activate the service set in
         * data-plenty-social=""
         *       Will be replaced with loaded share button
         *

         *
         * possible values for data-plenty-social:
         * "facebook-like"            : Load Facebooks "Like"-Button
         * "facebook-recommend"        : Load Facebooks "Recommend"-Button
         * "twitter"                : Load Twitter Button
         * "google-plus"            : Load google "+1"-Button
         *
         * Additional Tooltips
         * You can extend the parent element with a (bootstrap) tooltip by adding data-toggle="tooltip" and
         * title="TOOLTIP CONTENT" Tooltip will be destroyed after activating a social service
         * (!) Requires bootstrap.js
         *
         * Legacy directive selector: data-plenty-social
         *
         * @param elem
         */
        function toggleSocialShare( elem, socialShareService )
        {
            var $elem   = $( elem );
            var $toggle = $elem.find( '[data-plenty-rel="social-switch"]' );

            // append container to put / delete service.html
            $elem.append( '

' );

            // add "off" class to switch, if neither "off" or "on" is set
            // replaced hasClass() with is() benchmark: http://jsperf.com/hasclasstest
            if ( !$toggle.is( 'off, on' ) )
            {
                $toggle.addClass( 'off' );
            }

            // toggle switch
            $toggle.on( 'click', function()
            {
                if ( $toggle.hasClass( 'off' ) )
                {
                    // TODO remove bootstrap dependency
                    if ( $elem.attr( "data-toggle" ) == "tooltip" )
                    {
                        $elem.tooltip( 'destroy' )
                    }
                    $toggle.removeClass( 'off' ).addClass( 'on' );
                    // hide dummy button
                    $elem.find( '[data-plenty-rel="social-placeholder"]' ).hide();
                    // load HTML defined in 'api'
                    $elem.find( '.social-container' ).append( SocialShareService.getSocialService( socialShareService ) );
                }
                // do not disable social medias after activation
            } );
        }

        /**
         * Toggle Class
         * toggle style-classes on click
         * Usage / data-attribute:
         *

         * target    :    jQuery selector to toggle the class at.
         * class        :  class(es) to toggle at target element
         * media        :  only toggle class on given media sizes (optional)
         *
         * (!) using data-plenty-toggle on -elements will prevent redirecting to href=""
         *
         * Legacy directive selector: data-plenty-toggle
         *
         * @param cssClass
         * @param target
         * @param interval
         */
        function toggleClass( cssClass, target, interval )
        {
            var $target = $( target );
            /* FIXME
             * Callisto 3.1 Design adaption:
             * NavigationCategoriesList
             * Line 8
             * BEFORE:
             *

             * AFTER:
             *

             *
             * Line 10
             * BEFORE:
             *
             * AFTER:
             *
             *
             * */
            if ( $target.parents( ".navbar-main" ).length > 0 )
            {
                var $elem = $target.children( "span" );
                pm.directives["MobileDropdown"].slideDropdown( $elem );
                return true;
            }

            if ( !!target && !!cssClass && ( !interval || MediaSizeService.isInterval( interval ) ) )
            {
                var e = pm.getRecentEvent();
                if ( !!e )
                {
                    e.preventDefault();
                }

                $target.toggleClass( cssClass );
                return false;
            }
        }

        function addClass( cssClass, target, interval )
        {
            if ( !!target && !!cssClass && ( !interval || MediaSizeService.isInterval( interval ) ) )
            {
                var e = pm.getRecentEvent();
                if ( !!e )
                {
                    e.preventDefault();
                }

                $( target ).addClass( cssClass );
                return false;
            }
        }

        function removeClass( cssClass, target, interval )
        {
            if ( !!target && !!cssClass && ( !interval || MediaSizeService.isInterval( interval ) ) )
            {
                var e = pm.getRecentEvent();
                if ( !!e )
                {
                    e.preventDefault();
                }

                $( target ).removeClass( cssClass );
                return false;
            }
        }

        /*
         ##### PRIVATE FUNCTIONS ######
         */

        function fireEqualHeight()
        {
            for ( var i = equalHeightElementList.length - 1; i >= 0; i-- )
            {
                equalHeight( equalHeightElementList[i], '', true );
            }
        }

        function doToArrayElements( array, func, params )
        {
            for ( var i = array.length - 1; i >= 0; i-- )
            {
                array[i][func]( params );
            }
        }

    }, ['MediaSizeService', 'SocialShareService'] );
}( jQuery, PlentyFramework ));<|endoftext|>


<commit_before>export default {
  name: 'Area',
  props: [
    {
      name: 'type',
      type: `'basis' | 'basisClosed' | 'basisOpen' | 'linear' | 'linearClosed' | 'natural' |
      'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter' | Function`,
      defaultVal: '\'linear\'',
      isOptional: false,
      desc: {
        'en-US': 'The interpolation type of area. And customized interpolation function can be set to type.',
        'zh-CN': '曲线的插值类型，可以接收自定义的函数。折线对应的 type 为 "linear"。',
      },
      examples: [{
        name: 'd3-shape interpolation',
        url: 'https://github.com/d3/d3-shape#curves',
        isExternal: true,
      }, {
        name: 'An AreaChart which has two area with different interpolation.',
        url: '/examples/CardinalAreaChart',
      }],
    }, {
      name: 'dataKey',
      type: 'String | Number',
      defaultVal: 'undefined',
      isOptional: false,
      desc: {
        'en-US': 'The key of a group of data which should be unique in an area chart.',
        'zh-CN': '每个区域图对应一个唯一的 key，需要在 AreaChart 中保证唯一。',
      },
    }, {
      name: 'xAxisId',
      type: 'String | Number',
      defaultVal: '0',
      isOptional: false,
      desc: {
        'en-US': 'The id of x-axis which is corresponding to the data.',
        'zh-CN': '区域图对应的 x 轴的 id 。',
      },
    }, {
      name: 'yAxisId',
      type: 'String | Number',
      defaultVal: '0',
      isOptional: false,
      desc: {
        'en-US': 'The id of y-axis which is corresponding to the data.',
        'zh-CN': '区域图对应的 y 轴的 id 。',
      },
    }, {
      name: 'legendType',
      type: '\'line\' | \'square\' | \'rect\'| \'circle\' | \'cross\' | \'diamond\' | \'square\' | \'star\' | \'triangle\' | \'wye\' | \'none\'',
      defaultVal: '\'line\'',
      isOptional: true,
      desc: {
        'en-US': 'The type of icon in legend.  If set to \'none\', no legend item will be rendered.',
        'zh-CN': '对应的图例 icon 的类型。',
      },
    }, {
      name: 'dot',
      type: 'Boolean | Object | ReactElement | Function',
      defaultVal: 'false',
      isOptional: false,
      desc: {
        'en-US': 'If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally. If object set, dots will be drawn which have the props mergered by the internal calculated props and the option. If ReactElement set, the option can be the custom dot element. If set a function, the function will be called to render customized dot.',
        'zh-CN': '曲线上的点，接收多种配置。当值为 false ，不渲染点。当值为 true ，点会继承 Area 的属性配置，例如配置了 Area 的 stroke 为 "red"， 点会继承这个属性。当值为一个对象的时候，会把这个对象解析为点的属性，来覆盖默认属性。当值是一个 React Element ，会克隆这个 React Element 来渲染“点”。当值是函数时，会调用这个函数去渲染自定义的“点”。',
      },
      format: [
        '<Area dataKey="value" dot={false} />',
        '<Area dataKey="value" dot={{ stroke: \'red\', strokeWidth: 2 }} />',
        '<Area dataKey="value" dot={<CustomizedDot />} />',
        '<Area dataKey="value" dot={renderDot}/>',
      ],
    }, {
      name: 'activeDot',
      type: 'Boolean | Object | ReactElement | Function',
      defaultVal: 'true',
      isOptional: false,
      desc: {
        'en-US': 'The dot is shown when muser enter a area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. If object set, active dot will be drawn which have the props mergered by the internal calculated props and the option. If ReactElement set, the option can be the custom active dot element.If set a function, the function will be called to render customized active dot.',
        'zh-CN': '当图表触发 mouseenter 事件，并且需要展示 Tooltip 的时候，会展示一个“激活状态的点”。当值为 false ，不渲染“激活状态的点”。当值为 true ，点会继承 Area 的属性配置，例如配置了 Area 的 stroke 为 "red"， 点会继承这个属性。当值为一个对象的时候，会把这个对象解析为点的属性，来覆盖默认属性。当值是一个 React Element ，会克隆这个 React Element 来渲染“点”。当值是一个 函数 时，会调用这个函数去渲染自定义的“点”。',
      },
      format: [
        '<Area dataKey="value" activeDot={false} />',
        '<Area dataKey="value" activeDot={{ stroke: \'red\', strokeWidth: 2, r: 10 }} />',
        '<Area dataKey="value" activeDot={<CustomizedActiveDot />} />',
        '<Area dataKey="value" activeDot={renderDot} />',
      ],
    }, {
      name: 'label',
      type: 'Boolean | Object | ReactElement | Function',
      defaultVal: 'false',
      isOptional: false,
      desc: {
        'en-US': 'If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. If object set, labels will be drawn which have the props mergered by the internal calculated props and the option. If ReactElement set, the option can be the custom label element. If set a function, the function will be called to render customized label.',
        'zh-CN': '图形上的文本标签。当值为 false ，不展示文本标签。当值为 true，会根据 Area 的属性配置来展示文本标签。当值为对象的时候，会把这个对象解析为文本标签的属性，来覆盖默认属性。当值是一个 React Element ，会克隆这个 React Element 来渲染“文本标签”。当值是一个 函数 时，会调用这个函数去渲染自定义的“文本标签”。',
      },
      format: [
        '<Area dataKey="value" label />',
        '<Area dataKey="value" label={{ fill: \'red\', fontSize: 20 }} />',
        '<Area dataKey="value" label={<CustomizedLabel />} />',
        '<Area dataKey="value" label={renderLabel} />',
      ],
    }, {
      name: 'stroke',
      type: 'String',
      defaultVal: '#3182bd',
      isOptional: false,
      desc: {
        'en-US': 'The stroke color. If "none", no line will be drawn.',
        'zh-CN': '描边的颜色。当值为"none"时，不描边',
      },
    }, {
      name: 'layout',
      type: '\'horizontal\' | \'vertical\'',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The layout of area, usually inherited from parent.',
        'zh-CN': '布局类型，通常继承父组件的布局类型。',
      },
    }, {
      name: 'baseLine',
      type: 'Number | Array',
      defaultVal: 'undefined',
      isOptional: false,
      desc: {
        'en-US': 'The value which can describle the line, usually calculated internally.',
        'zh-CN': '基准线，可以是一个数值，这种情况会根据 layout 解析成 x = ${baseLine} 或者 y = ${baseLine}。当使用 AreaChart 或者 ComposedChart 作为父组件的时候，不需要自己计算，父组件会计算好。',
      },
      format: [
        '<Area dataKey="value" baseLine={8} />',
        '<Area dataKey="value" baseLine={[{ x: 12, y: 15 }]} />',
      ],
    }, {
      name: 'points',
      type: 'Array',
      defaultVal: 'undefined',
      isOptional: false,
      desc: {
        'en-US': 'The coordinates of all the points in the area, usually calculated internally.',
        'zh-CN': '曲线上点的坐标。当使用 AreaChart 或者 ComposedChart 作为父组件的时候，不需要自己计算，父组件会计算好。',
      },
      format: ['[{ x: 12, y: 12, value: 240 }]'],
    }, {
      name: 'stackId',
      type: 'String | Number',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The stack id of area, when two areas have the same value axis and same stackId, then the two areas area stacked in order.',
        'zh-CN': '堆积id，当两组及以上的区域图有相同的数值轴以及相同的 stackId 时，这些区域图会按照顺序堆积展示。',
      },
      format: [`
<AreaChart data={data} width={400} height={300}>
  <Area stackId="pv" dataKey="pv01" />
  <Area stackId="pv" dataKey="pv02" />
  <Area stackId="uv" dataKey="uv01" />
  <Area stackId="uv" dataKey="uv02" />
</AreaChart>
`,
      ],
      examples: [{
        name: 'A stacked area chart',
        url: '/examples/StackedAreaChart',
      }],
    }, {
      name: 'connectNulls',
      type: 'Boolean',
      defaultVal: 'false',
      isOptional: false,
      desc: {
        'en-US': 'Whether to connect a graph area across null points.',
        'zh-CN': '当遇到 null、undefined 等非法输入的时候，是否跳过这个值，让前后连接起来。',
      },
      examples: [{
        name: 'An areaChart connect nulls and an areaChart disconnect nulls',
        url: '/examples/AreaChartConnectNulls',
      }],
    }, {
      name: 'unit',
      type: 'String | Number',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The unit of data. This option will be used in tooltip.',
        'zh-CN': '对应数据的单位，这个单位会展示在 Tooltip 的数值后面。',
      },
    }, {
      name: 'name',
      type: 'String | Number',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The name of data. This option will be used in tooltip and legend to represent a area. If no value was set to this option, the value of dataKey will be used alternatively.',
        'zh-CN': '对应数据的名称，这个名称会展示在 Tooltip 以及 Legend 中用来表示这个区域图。如果名称为空，我们会使用 dataKey 替代。',
      },
    }, {
      name: 'isAnimationActive',
      type: 'Boolean',
      defaultVal: 'true in CSR, and false in SSR',
      isOptional: false,
      desc: {
        'en-US': 'If set false, animation of area will be disabled.',
        'zh-CN': '当值为 false，不开启动画。',
      },
    }, {
      name: 'animationBegin',
      type: 'Number',
      defaultVal: 0,
      isOptional: false,
      desc: {
        'en-US': 'Specifies when the animation should begin, the unit of this option is ms.',
        'zh-CN': '声明组件挂载或更新后，开始运行动画的间隔时长，单位为毫秒。',
      },
    }, {
      name: 'animationDuration',
      type: 'Number',
      defaultVal: 1500,
      isOptional: false,
      desc: {
        'en-US': 'Specifies the duration of animation, the unit of this option is ms.',
        'zh-CN': '声明动画的运行时长，单位为毫秒。',
      },
    }, {
      name: 'animationEasing',
      type: '\'ease\' | \'ease-in\' | \'ease-out\' | \'ease-in-out\' | \'linear\'',
      defaultVal: '\'ease\'',
      isOptional: false,
      desc: {
        'en-US': 'The type of easing function.',
        'zh-CN': '动画缓动函数的类型。',
      },
    }, {
      name: 'id',
      type: 'String',
      defaultVal: 'null',
      isOptional: true,
      desc: {
        'en-US': 'The unique id of this component, which will be used to generate unique clip path id internally. This props is suggested to be set in SSR.',
        'zh-CN': '唯一的id，会用于生成内部的clip path id 等，建议使用SSR的时候设置这个属性。',
      },
    }, {
      name: 'onAnimationStart',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of animation start',
        'zh-CN': '区域图动画 start 事件的回调函数。',
      },
    }, {
      name: 'onAnimationEnd',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of animation end',
        'zh-CN': '区域图动画 end 事件的回调函数。',
      },
    }, {
      name: 'onClick',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of click on the area in this group',
        'zh-CN': '区域图 click 事件的回调函数。',
      },
      examples: [{
        name: 'A BarChart with customized click event handler',
        url: '/examples/BarChartWithCustomizedEvent',
      }],
    }, {
      name: 'onMouseDown',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mousedown on the area in this group',
        'zh-CN': '区域图 mousedown 事件的回调函数。',
      },
    }, {
      name: 'onMouseUp',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseup on the area in this group',
        'zh-CN': '区域图 mouseup 事件的回调函数。',
      },
    }, {
      name: 'onMouseMove',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mousemove on the area in this group',
        'zh-CN': '区域图 mousemove 事件的回调函数。',
      },
    }, {
      name: 'onMouseOver',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseover on the area in this group',
        'zh-CN': '区域图 mouseover 事件的回调函数。',
      },
    }, {
      name: 'onMouseOut',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseout on the area in this group',
        'zh-CN': '区域图 mouseout 事件的回调函数。',
      },
    }, {
      name: 'onMouseEnter',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of moustenter on the area in this group',
        'zh-CN': '区域图 moustenter 事件的回调函数。',
      },
    }, {
      name: 'onMouseLeave',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseleave on the area in this group',
        'zh-CN': '区域图 mouseleave 事件的回调函数。',
      },
    },
  ],
  parentComponents: [
    'AreaChart', 'ComposedChart',
  ],
  childrenComponents: [
    'LabelList',
  ],
};
<commit_msg>Added Area component DataKey type Function<commit_after>export default {
  name: 'Area',
  props: [
    {
      name: 'type',
      type: `'basis' | 'basisClosed' | 'basisOpen' | 'linear' | 'linearClosed' | 'natural' |
      'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter' | Function`,
      defaultVal: '\'linear\'',
      isOptional: false,
      desc: {
        'en-US': 'The interpolation type of area. And customized interpolation function can be set to type.',
        'zh-CN': '曲线的插值类型，可以接收自定义的函数。折线对应的 type 为 "linear"。',
      },
      examples: [{
        name: 'd3-shape interpolation',
        url: 'https://github.com/d3/d3-shape#curves',
        isExternal: true,
      }, {
        name: 'An AreaChart which has two area with different interpolation.',
        url: '/examples/CardinalAreaChart',
      }],
    }, {
      name: 'dataKey',
      type: 'String | Number | Function',
      defaultVal: 'undefined',
      isOptional: false,
      desc: {
        'en-US': 'The key of a group of data which should be unique in an area chart.',
        'zh-CN': '每个区域图对应一个唯一的 key，需要在 AreaChart 中保证唯一。',
      },
    }, {
      name: 'xAxisId',
      type: 'String | Number',
      defaultVal: '0',
      isOptional: false,
      desc: {
        'en-US': 'The id of x-axis which is corresponding to the data.',
        'zh-CN': '区域图对应的 x 轴的 id 。',
      },
    }, {
      name: 'yAxisId',
      type: 'String | Number',
      defaultVal: '0',
      isOptional: false,
      desc: {
        'en-US': 'The id of y-axis which is corresponding to the data.',
        'zh-CN': '区域图对应的 y 轴的 id 。',
      },
    }, {
      name: 'legendType',
      type: '\'line\' | \'square\' | \'rect\'| \'circle\' | \'cross\' | \'diamond\' | \'square\' | \'star\' | \'triangle\' | \'wye\' | \'none\'',
      defaultVal: '\'line\'',
      isOptional: true,
      desc: {
        'en-US': 'The type of icon in legend.  If set to \'none\', no legend item will be rendered.',
        'zh-CN': '对应的图例 icon 的类型。',
      },
    }, {
      name: 'dot',
      type: 'Boolean | Object | ReactElement | Function',
      defaultVal: 'false',
      isOptional: false,
      desc: {
        'en-US': 'If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally. If object set, dots will be drawn which have the props mergered by the internal calculated props and the option. If ReactElement set, the option can be the custom dot element. If set a function, the function will be called to render customized dot.',
        'zh-CN': '曲线上的点，接收多种配置。当值为 false ，不渲染点。当值为 true ，点会继承 Area 的属性配置，例如配置了 Area 的 stroke 为 "red"， 点会继承这个属性。当值为一个对象的时候，会把这个对象解析为点的属性，来覆盖默认属性。当值是一个 React Element ，会克隆这个 React Element 来渲染“点”。当值是函数时，会调用这个函数去渲染自定义的“点”。',
      },
      format: [
        '<Area dataKey="value" dot={false} />',
        '<Area dataKey="value" dot={{ stroke: \'red\', strokeWidth: 2 }} />',
        '<Area dataKey="value" dot={<CustomizedDot />} />',
        '<Area dataKey="value" dot={renderDot}/>',
      ],
    }, {
      name: 'activeDot',
      type: 'Boolean | Object | ReactElement | Function',
      defaultVal: 'true',
      isOptional: false,
      desc: {
        'en-US': 'The dot is shown when muser enter a area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. If object set, active dot will be drawn which have the props mergered by the internal calculated props and the option. If ReactElement set, the option can be the custom active dot element.If set a function, the function will be called to render customized active dot.',
        'zh-CN': '当图表触发 mouseenter 事件，并且需要展示 Tooltip 的时候，会展示一个“激活状态的点”。当值为 false ，不渲染“激活状态的点”。当值为 true ，点会继承 Area 的属性配置，例如配置了 Area 的 stroke 为 "red"， 点会继承这个属性。当值为一个对象的时候，会把这个对象解析为点的属性，来覆盖默认属性。当值是一个 React Element ，会克隆这个 React Element 来渲染“点”。当值是一个 函数 时，会调用这个函数去渲染自定义的“点”。',
      },
      format: [
        '<Area dataKey="value" activeDot={false} />',
        '<Area dataKey="value" activeDot={{ stroke: \'red\', strokeWidth: 2, r: 10 }} />',
        '<Area dataKey="value" activeDot={<CustomizedActiveDot />} />',
        '<Area dataKey="value" activeDot={renderDot} />',
      ],
    }, {
      name: 'label',
      type: 'Boolean | Object | ReactElement | Function',
      defaultVal: 'false',
      isOptional: false,
      desc: {
        'en-US': 'If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. If object set, labels will be drawn which have the props mergered by the internal calculated props and the option. If ReactElement set, the option can be the custom label element. If set a function, the function will be called to render customized label.',
        'zh-CN': '图形上的文本标签。当值为 false ，不展示文本标签。当值为 true，会根据 Area 的属性配置来展示文本标签。当值为对象的时候，会把这个对象解析为文本标签的属性，来覆盖默认属性。当值是一个 React Element ，会克隆这个 React Element 来渲染“文本标签”。当值是一个 函数 时，会调用这个函数去渲染自定义的“文本标签”。',
      },
      format: [
        '<Area dataKey="value" label />',
        '<Area dataKey="value" label={{ fill: \'red\', fontSize: 20 }} />',
        '<Area dataKey="value" label={<CustomizedLabel />} />',
        '<Area dataKey="value" label={renderLabel} />',
      ],
    }, {
      name: 'stroke',
      type: 'String',
      defaultVal: '#3182bd',
      isOptional: false,
      desc: {
        'en-US': 'The stroke color. If "none", no line will be drawn.',
        'zh-CN': '描边的颜色。当值为"none"时，不描边',
      },
    }, {
      name: 'layout',
      type: '\'horizontal\' | \'vertical\'',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The layout of area, usually inherited from parent.',
        'zh-CN': '布局类型，通常继承父组件的布局类型。',
      },
    }, {
      name: 'baseLine',
      type: 'Number | Array',
      defaultVal: 'undefined',
      isOptional: false,
      desc: {
        'en-US': 'The value which can describle the line, usually calculated internally.',
        'zh-CN': '基准线，可以是一个数值，这种情况会根据 layout 解析成 x = ${baseLine} 或者 y = ${baseLine}。当使用 AreaChart 或者 ComposedChart 作为父组件的时候，不需要自己计算，父组件会计算好。',
      },
      format: [
        '<Area dataKey="value" baseLine={8} />',
        '<Area dataKey="value" baseLine={[{ x: 12, y: 15 }]} />',
      ],
    }, {
      name: 'points',
      type: 'Array',
      defaultVal: 'undefined',
      isOptional: false,
      desc: {
        'en-US': 'The coordinates of all the points in the area, usually calculated internally.',
        'zh-CN': '曲线上点的坐标。当使用 AreaChart 或者 ComposedChart 作为父组件的时候，不需要自己计算，父组件会计算好。',
      },
      format: ['[{ x: 12, y: 12, value: 240 }]'],
    }, {
      name: 'stackId',
      type: 'String | Number',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The stack id of area, when two areas have the same value axis and same stackId, then the two areas area stacked in order.',
        'zh-CN': '堆积id，当两组及以上的区域图有相同的数值轴以及相同的 stackId 时，这些区域图会按照顺序堆积展示。',
      },
      format: [`
<AreaChart data={data} width={400} height={300}>
  <Area stackId="pv" dataKey="pv01" />
  <Area stackId="pv" dataKey="pv02" />
  <Area stackId="uv" dataKey="uv01" />
  <Area stackId="uv" dataKey="uv02" />
</AreaChart>
`,
      ],
      examples: [{
        name: 'A stacked area chart',
        url: '/examples/StackedAreaChart',
      }],
    }, {
      name: 'connectNulls',
      type: 'Boolean',
      defaultVal: 'false',
      isOptional: false,
      desc: {
        'en-US': 'Whether to connect a graph area across null points.',
        'zh-CN': '当遇到 null、undefined 等非法输入的时候，是否跳过这个值，让前后连接起来。',
      },
      examples: [{
        name: 'An areaChart connect nulls and an areaChart disconnect nulls',
        url: '/examples/AreaChartConnectNulls',
      }],
    }, {
      name: 'unit',
      type: 'String | Number',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The unit of data. This option will be used in tooltip.',
        'zh-CN': '对应数据的单位，这个单位会展示在 Tooltip 的数值后面。',
      },
    }, {
      name: 'name',
      type: 'String | Number',
      defaultVal: 'undefined',
      isOptional: true,
      desc: {
        'en-US': 'The name of data. This option will be used in tooltip and legend to represent a area. If no value was set to this option, the value of dataKey will be used alternatively.',
        'zh-CN': '对应数据的名称，这个名称会展示在 Tooltip 以及 Legend 中用来表示这个区域图。如果名称为空，我们会使用 dataKey 替代。',
      },
    }, {
      name: 'isAnimationActive',
      type: 'Boolean',
      defaultVal: 'true in CSR, and false in SSR',
      isOptional: false,
      desc: {
        'en-US': 'If set false, animation of area will be disabled.',
        'zh-CN': '当值为 false，不开启动画。',
      },
    }, {
      name: 'animationBegin',
      type: 'Number',
      defaultVal: 0,
      isOptional: false,
      desc: {
        'en-US': 'Specifies when the animation should begin, the unit of this option is ms.',
        'zh-CN': '声明组件挂载或更新后，开始运行动画的间隔时长，单位为毫秒。',
      },
    }, {
      name: 'animationDuration',
      type: 'Number',
      defaultVal: 1500,
      isOptional: false,
      desc: {
        'en-US': 'Specifies the duration of animation, the unit of this option is ms.',
        'zh-CN': '声明动画的运行时长，单位为毫秒。',
      },
    }, {
      name: 'animationEasing',
      type: '\'ease\' | \'ease-in\' | \'ease-out\' | \'ease-in-out\' | \'linear\'',
      defaultVal: '\'ease\'',
      isOptional: false,
      desc: {
        'en-US': 'The type of easing function.',
        'zh-CN': '动画缓动函数的类型。',
      },
    }, {
      name: 'id',
      type: 'String',
      defaultVal: 'null',
      isOptional: true,
      desc: {
        'en-US': 'The unique id of this component, which will be used to generate unique clip path id internally. This props is suggested to be set in SSR.',
        'zh-CN': '唯一的id，会用于生成内部的clip path id 等，建议使用SSR的时候设置这个属性。',
      },
    }, {
      name: 'onAnimationStart',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of animation start',
        'zh-CN': '区域图动画 start 事件的回调函数。',
      },
    }, {
      name: 'onAnimationEnd',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of animation end',
        'zh-CN': '区域图动画 end 事件的回调函数。',
      },
    }, {
      name: 'onClick',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of click on the area in this group',
        'zh-CN': '区域图 click 事件的回调函数。',
      },
      examples: [{
        name: 'A BarChart with customized click event handler',
        url: '/examples/BarChartWithCustomizedEvent',
      }],
    }, {
      name: 'onMouseDown',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mousedown on the area in this group',
        'zh-CN': '区域图 mousedown 事件的回调函数。',
      },
    }, {
      name: 'onMouseUp',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseup on the area in this group',
        'zh-CN': '区域图 mouseup 事件的回调函数。',
      },
    }, {
      name: 'onMouseMove',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mousemove on the area in this group',
        'zh-CN': '区域图 mousemove 事件的回调函数。',
      },
    }, {
      name: 'onMouseOver',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseover on the area in this group',
        'zh-CN': '区域图 mouseover 事件的回调函数。',
      },
    }, {
      name: 'onMouseOut',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseout on the area in this group',
        'zh-CN': '区域图 mouseout 事件的回调函数。',
      },
    }, {
      name: 'onMouseEnter',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of moustenter on the area in this group',
        'zh-CN': '区域图 moustenter 事件的回调函数。',
      },
    }, {
      name: 'onMouseLeave',
      type: 'Function',
      isOptional: true,
      desc: {
        'en-US': 'The customized event handler of mouseleave on the area in this group',
        'zh-CN': '区域图 mouseleave 事件的回调函数。',
      },
    },
  ],
  parentComponents: [
    'AreaChart', 'ComposedChart',
  ],
  childrenComponents: [
    'LabelList',
  ],
};
<|endoftext|>


<commit_before>var utils 		= require('./dom-utils'),
	jsdom 		= require('jsdom'),
	juice 		= require('juice'),
	NodeCache 	= require('node-cache'),
	extractor 	= module.exports;

var nodeCache 	= new NodeCache();

function jsdomCallback(cacheKey, document, callback) {
	juice.juiceDocument(document, {
		url: 'fake'
	}, function() {
		if(cacheKey !== null){
			nodeCache.set(cacheKey, document.body.innerHTML);
		}
		callback(document.body.innerHTML);
	});
}

extractor.fetch = function(data, selector, callback) {
	var cacheKey 	= null;
	var isValidUrl 	= utils.isValidUrl(data);

	if(isValidUrl){
		cacheKey 		= data + '#' + selector;
		var cachedValue = nodeCache.get(cacheKey);

		if(cachedValue[cacheKey] !== undefined){
			callback(cachedValue[cacheKey]);
			return;
		}
	}

	if (selector instanceof Function) {
		callback = selector;
		selector = 'body';
	} else if (typeof selector !== 'string') {
		selector = 'body';
	}

	var jsdomconfig = {
		scripts: ["http://code.jquery.com/jquery.js"],
		done: function(errors, window) {
			var $ = window.$;
			$('body').html($(selector).wrap('<span/>').parent().html());
			$('script').remove();

			if (isValidUrl) {
				$('link').each(function() {
					$(this).attr('href', utils.relToAbs(data, $(this).attr('href')));
				});
				$('img').each(function() {
					$(this).attr('src', utils.relToAbs(data, $(this).attr('src')));
				});
				$('a').each(function() {
					$(this).attr('href', utils.relToAbs(data, $(this).attr('href')));
				});
				$('form').each(function() {
					$(this).attr('action', utils.relToAbs(data, $(this).attr('action')));
				});
			}
			jsdomCallback(cacheKey, window.document, callback);
		}
	};

	if (isValidUrl) {
		if (data.indexOf('http') !== 0) {
			data = 'http://' + data;
		}
		jsdomconfig.url = data;
	} else if (typeof data !== 'string') {
		if (data instanceof Function) {
			callback();
		}
		return;
	} else {
		jsdomconfig.html = data;
	}

	jsdom.env(jsdomconfig);
};

extractor.middleware = function(options) {
	return function(req, res, next) {
		var params = require('url').parse(req.url, true).query;
		if (params.url !== undefined && params.selector !== undefined) {
			extractor.fetch(params.url, params.selector, function(response) {
				res.write(response);
				res.end();
			});
		} else {
			next();
		}
	};
};<commit_msg>Allow # selectors, using |sharp|<commit_after>var utils = require('./dom-utils'),
	jsdom = require('jsdom'),
	juice = require('juice'),
	NodeCache = require('node-cache'),
	extractor = module.exports;

var nodeCache = new NodeCache();

function jsdomCallback(cacheKey, document, callback) {
	if (document.body === null) {
		callback('');
		return;
	}

	juice.juiceDocument(document, {
		url: 'fake'
	}, function() {

		if (cacheKey !== null) {
			nodeCache.set(cacheKey, document.body.innerHTML);
		}
		callback(document.body.innerHTML);
	});
}

function cleanRelativePathes($, absoluteUrl) {
	$('link').each(function() {
		$(this).attr('href', utils.relToAbs(absoluteUrl, $(this).attr('href')));
	});
	$('img').each(function() {
		$(this).attr('src', utils.relToAbs(absoluteUrl, $(this).attr('src')));
	});
	$('a').each(function() {
		$(this).attr('href', utils.relToAbs(absoluteUrl, $(this).attr('href')));
	});
	$('form').each(function() {
		$(this).attr('action', utils.relToAbs(absoluteUrl, $(this).attr('action')));
	});
}

extractor.fetch = function(data, selector, callback) {
	var cacheKey = null;
	var isValidUrl = utils.isValidUrl(data);

	if (isValidUrl) {
		cacheKey = data + '#' + selector;
		var cachedValue = nodeCache.get(cacheKey);

		if (cachedValue[cacheKey] !== undefined) {
			callback(cachedValue[cacheKey]);
			return;
		}
	}

	if (selector instanceof Function) {
		callback = selector;
		selector = 'body';
	} else if (typeof selector !== 'string' || selector === '/') {
		selector = 'body';
	} else {
		selector = selector.replace('\\', '');
	}

	var jsdomconfig = {
		scripts: ["http://code.jquery.com/jquery.js"],
		done: function(errors, window) {
			var $ = window.$;

			$('body').html($(selector).wrap('<span/>').parent().html());
			$('script').remove();

			if (isValidUrl) {
				cleanRelativePathes($, data);
			}
			jsdomCallback(cacheKey, window.document, callback);
		}
	};

	if (isValidUrl) {
		if (data.indexOf('http') !== 0) {
			data = 'http://' + data;
		}
		jsdomconfig.url = data;
	} else if (typeof data !== 'string') {
		if (data instanceof Function) {
			callback();
		}
		return;
	} else {
		jsdomconfig.html = data;
	}

	jsdom.env(jsdomconfig);
};

extractor.middleware = function(options) {
	return function(req, res, next) {
		var params = require('url').parse(req.url, true).query;
		if (params.url !== undefined && params.selector !== undefined) {
			params.selector = params.selector.replace('|sharp|', '#');
			extractor.fetch(params.url, params.selector, function(response) {
				res.write(response);
				res.end();
			});
		} else {
			next();
		}
	};
};<|endoftext|>


<commit_before>var _ = require('lodash');

var inspectors = [
    inspectLinks,
    inspectImages
];

function inspect(dom) {
    var pageInfo = inspectHelper(dom, {});
    Object.keys(pageInfo).forEach(function(infoType) {
        pageInfo[infoType] = _.unique(pageInfo[infoType]);
    });
    return pageInfo;

    function inspectHelper(dom, pageAttributes) {
        return dom.reduce(function(pageAttributes, domNode) {
            inspectors.forEach(function(inspector) {
                inspector(pageAttributes, domNode);
            });

            if (domNode.children) {
                pageAttributes = inspectHelper(domNode.children, pageAttributes);
            }
            return pageAttributes;
        }, pageAttributes);
    }
}

function inspectLinks(pageAttributes, domNode) {
    if (domNode.name && domNode.name === 'a' &&
        domNode.attribs && domNode.attribs.href) {
        var href = domNode.attribs.href;
        if (pageAttributes.links) {
            pageAttributes.links.push(href);
        } else {
            pageAttributes.links = [href];
        }
    }
}

function inspectImages(pageAttributes, domNode) {
    if (domNode.name && domNode.name === 'img' &&
        domNode.attribs) {
        var image = domNode.attribs;
        if (pageAttributes.images) {
            pageAttributes.images.push(image);
        } else {
            pageAttributes.images = [image];
        }
    }
}

module.exports = {
    inspect: inspect
};
<commit_msg>Can now inspect script nodes<commit_after>var _ = require('lodash');

var inspectors = [
    inspectLinks,
    inspectImages,
    inspectScript
];

function inspect(dom) {
    var pageInfo = inspectHelper(dom, {});
    Object.keys(pageInfo).forEach(function(infoType) {
        pageInfo[infoType] = _.unique(pageInfo[infoType]);
    });
    return pageInfo;

    function inspectHelper(dom, pageAttributes) {
        return dom.reduce(function(pageAttributes, domNode) {
            inspectors.forEach(function(inspector) {
                inspector(pageAttributes, domNode);
            });

            if (domNode.children) {
                pageAttributes = inspectHelper(domNode.children, pageAttributes);
            }
            return pageAttributes;
        }, pageAttributes);
    }
}

function inspectLinks(pageAttributes, domNode) {
    if (domNode.name && domNode.name === 'a' &&
        domNode.attribs && domNode.attribs.href) {
        var href = domNode.attribs.href;
        if (pageAttributes.links) {
            pageAttributes.links.push(href);
        } else {
            pageAttributes.links = [href];
        }
    }
}

function inspectImages(pageAttributes, domNode) {
    if (domNode.name && domNode.name === 'img' &&
        domNode.attribs && !_.isEmpty(domNode.attribs)) {
        var image = domNode.attribs;
        if (pageAttributes.images) {
            pageAttributes.images.push(image);
        } else {
            pageAttributes.images = [image];
        }
    }
}

function inspectScript(pageAttributes, domNode) {
    if (domNode.type && domNode.type === 'script' &&
        domNode.attribs && !_.isEmpty(domNode.attribs)) {
        var script = domNode.attribs;
        if (pageAttributes.scripts) {
            pageAttributes.scripts.push(script);
        } else {
            pageAttributes.scripts = [script];
        }
    }
}

module.exports = {
    inspect: inspect
};
<|endoftext|>


<commit_before>/**
 * dom/view/base.js
 */

var baseView = {
    make: function (opts) {
        var instance = {};
        _.extend(instance, this);
        instance['initialize'](opts);
        return instance;
    },
    'extend': function (subclass) {
        return _.extend({}, this, subclass, { parentView: this });
    },

    '$': function(selector) {
        return this['$el'].find(selector);
    },

    isView: true,

    'initialize': function (opts) {
        var self = this;
        uniqueId(self);
        _.extend(self, opts);
        self['$el'] = $(self['el']);
        self['el']['view'] = self;
        self.priority = self.domParentView ? self.domParentView.priority - 1 : BASE_PRIORITY_VIEW;
        self.scope = autorun(self.render, self, self.priority, 'view_' + self['Name'],
                             self.onScopeExecute, self, true);
    },

    onScopeExecute: function (scope) {
        log(INFO, this, 'lookups', scope.lookups);
    },

    /**
     * View.destroy
     *
     * Destroys this view, removing all bindings and sub-views (recursively).
     */
    destroy: function (destroyRoot) {
        var self = this;
        log(VERBOSE, self, 'destroy', 'due to re-render of ' + destroyRoot['Name']);
        self.destroyed = true;
        self.scope.destroy();
        _.each(self.subViews || [], function (view) {
            view.destroy(self);
        });
        self['destroyDOM'](self.$el);
    },

    /**
     * View.render
     *
     * This function is called at View init, and again whenever any model properties that this View
     * depended on are changed.
     */
    render: function () {
        var self = this;
        // This view may get a reset call at the same instant that another
        // view gets created to replace it.
        if (!self.destroyed) {
            /**
             * Move all this view's children to another temporary DOM element.  This will be used as the
             * pseudo-parent element for the destroyDOM call.
             */
            if (self.templateId) {
                /**
                 * If the DOM fragment to be removed has an active (focused) element, we attempt
                 * to restore that focus after refreshing this DOM fragment.  We also attempt
                 * to restore the selection start/end, which only works in Webkit/Gecko right
                 * now; see the URL below for possible IE compatibility.
                 */
                var activeElement = document.activeElement;
                var activeElementSelector, activeElementIndex, selectionStart, selectionEnd;
                if (_.contains($(activeElement).parents(), self.el)) {
                    // XXX this could be improved to pick up on IDs/classes/attributes or something?
                    activeElementSelector = 'input';
                    activeElementIndex = _.indexOf(self.$(activeElementSelector), activeElement);
                    // XXX for IE compatibility, this might work:
                    // http://the-stickman.com/web-development/javascript/ ...
                    // finding-selection-cursor-position-in-a-textarea-in-internet-explorer/
                    selectionStart = activeElement.selectionStart;
                    selectionEnd = activeElement.selectionEnd;
                }

                var $old = $('<div>').append(this.$el.children());
                var newHtml = renderTemplate(self.templateId, self);
                log(INFO, self, 'newhtml', newHtml);
                self.$el.html(newHtml);

                /**
                 * Execute the "fragment ready" callback.
                 */
                self['ready']();
                self['postReady']();

                /**
                 * (Re-)create sub-views for each descendent element with a tbone attribute.
                 * On re-renders, the pre-existing list of sub-views is passed to render, which
                 * attempts to pair already-rendered views with matching elements in this view's
                 * newly re-rendered template.  Matching views are transferred to the new DOM
                 * hierarchy without disruption.
                 */
                var oldSubViews = self.subViews || [];
                self.subViews = render(self.$('[tbone]'), self, oldSubViews);
                var obsoleteSubViews = _.difference(oldSubViews, self.subViews);
                /**
                 * Destroy all of the sub-views that were not reused.
                 */
                _.each(obsoleteSubViews, function (view) {
                    view.destroy(self);
                });
                /**
                 * Call destroyDOM with the the pseudo-parent created above.  This DOM fragment contains all
                 * of the previously-rendered (if any) DOM structure of this view and subviews, minus any
                 * subviews that are being reused (which have already been moved to the new parent).
                 */
                self['destroyDOM']($old);

                /**
                 * If we saved it above, restore the active element focus and selection.
                 */
                if (activeElementSelector) {
                    var newActiveElement = self.$(activeElementSelector)[activeElementIndex];
                    if (newActiveElement) {
                        $(newActiveElement).focus();
                        if (selectionStart != null && selectionEnd != null) {
                            newActiveElement.selectionStart = selectionStart;
                            newActiveElement.selectionEnd = selectionEnd;
                        }
                    }
                }
            } else {
                self['ready']();
                self['postReady']();
            }
            self['postRender']();
            viewRenders++;
        }
    },

    /**
     * View.ready
     *
     * The "template-ready" callback.  This is the restricted tbone equivalent of document-ready.
     * It is the recommended means of adding interactivity/data/whatever to Views.
     *
     * At the moment this callback is executed, subviews are neither rendered nor are they
     * attached to the DOM fragment.  If you need to interact with subviews, use postRender.
     */
    'ready': noop,

    /**
     * View.postReady
     *
     * This is the same as ready, except that it executes after ready.  The typical use case is
     * to override this in your base template to provide automatic application-wide helpers,
     * such as activating a tooltip library, and to use View.ready for specific view logic.
     */
    'postReady': noop,

    /**
     * View.postRender
     *
     * The "fragment-updated" callback.  This is executed whenever this view is re-rendered,
     * and after all sub-views (recursively) have rendered.
     *
     * Note that because we optimistically re-use sub-views, this may be called multiple times
     * with the same sub-view DOM fragments.  Ensure that anything you do to DOM elements in
     * sub-views is idempotent.
     */
    'postRender': noop,

    /**
     * View.destroyDOM
     *
     * The "document-destroy" callback.  Use this to do cleanup on removal of old HTML, e.g.
     * destroying associated tooltips.
     *
     * Note: Destroy contents of the $el parameter, not this.$el!  (XXX make this less error-prone)
     *
     * @param  {!jQuery} $el jQuery selection of DOM fragment to destroy
     */
    'destroyDOM': function ($el) { },

    /**
     * If a root attribute was specified, use that as the root object for this view's
     * render, both in templating automatically as well as available via this.root in
     * `ready` and `postRender` callbacks.
     */
    root: function () {
        return this['query'](DONT_GET_DATA);
    },

    /**
     * Perform a query relative to the view's rootObj and rootStr, delegating to
     * rootObj for the actual query but prepending rootStr to the prop string.
     **/
    'query': function (flag, prop, value) {
        var isSet = false;
        if (typeof flag !== 'number') {
            /**
             * If no flag provided, shift the prop and value over.  We do it this way instead
             * of having flag last so that we can type-check flag and discern optional flags
             * from optional values.  And flag should only be used internally, anyway.
             */
            value = prop;
            prop = flag;
            flag = 0;
            /**
             * Use arguments.length to switch to set mode in order to properly support
             * setting undefined.
             */
            isSet = arguments.length === 2;
        }
        prop = (this.rootStr ? this.rootStr + '.' : '') + (prop || '');
        return isSet ? this.rootObj(flag, prop, value) : this.rootObj(flag, prop);
    },

    'parentRoot': function () {
        return this.domParentView && this.domParentView.root();
    },

    /**
     * Get the DOM parent view, i.e. the view associated with the closest
     * ancestor DOM node that is a view root element.
     */
    'parent': function () {
        return this.domParentView;
    },

    // These are used at template render.  They're really not properties of views so much
    // as it is useful to reference these functions on the view, which is what we pass to
    // _.template already.
    'getHashId': getHashId,
    'isQueryable': isQueryable,
    'denullText': denullText

};

var defaultView = baseView;
/**
 * Set the default View to use when rendering templates with no matching View.
 * @param {ViewPrototype} view
 */
function setDefaultView(view) {
    defaultView = view;
}
<commit_msg>Prevent DOM JS error from firing on view re-render with a submit/button input focused<commit_after>/**
 * dom/view/base.js
 */

var baseView = {
    make: function (opts) {
        var instance = {};
        _.extend(instance, this);
        instance['initialize'](opts);
        return instance;
    },
    'extend': function (subclass) {
        return _.extend({}, this, subclass, { parentView: this });
    },

    '$': function(selector) {
        return this['$el'].find(selector);
    },

    isView: true,

    'initialize': function (opts) {
        var self = this;
        uniqueId(self);
        _.extend(self, opts);
        self['$el'] = $(self['el']);
        self['el']['view'] = self;
        self.priority = self.domParentView ? self.domParentView.priority - 1 : BASE_PRIORITY_VIEW;
        self.scope = autorun(self.render, self, self.priority, 'view_' + self['Name'],
                             self.onScopeExecute, self, true);
    },

    onScopeExecute: function (scope) {
        log(INFO, this, 'lookups', scope.lookups);
    },

    /**
     * View.destroy
     *
     * Destroys this view, removing all bindings and sub-views (recursively).
     */
    destroy: function (destroyRoot) {
        var self = this;
        log(VERBOSE, self, 'destroy', 'due to re-render of ' + destroyRoot['Name']);
        self.destroyed = true;
        self.scope.destroy();
        _.each(self.subViews || [], function (view) {
            view.destroy(self);
        });
        self['destroyDOM'](self.$el);
    },

    /**
     * View.render
     *
     * This function is called at View init, and again whenever any model properties that this View
     * depended on are changed.
     */
    render: function () {
        var self = this;
        // This view may get a reset call at the same instant that another
        // view gets created to replace it.
        if (!self.destroyed) {
            /**
             * Move all this view's children to another temporary DOM element.  This will be used as the
             * pseudo-parent element for the destroyDOM call.
             */
            if (self.templateId) {
                /**
                 * If the DOM fragment to be removed has an active (focused) element, we attempt
                 * to restore that focus after refreshing this DOM fragment.  We also attempt
                 * to restore the selection start/end, which only works in Webkit/Gecko right
                 * now; see the URL below for possible IE compatibility.
                 */
                var activeElement = document.activeElement;
                var activeElementSelector, activeElementIndex, selectionStart, selectionEnd;
                if (_.contains($(activeElement).parents(), self.el)) {
                    // XXX this could be improved to pick up on IDs/classes/attributes or something?
                    activeElementSelector = 'input';
                    activeElementIndex = _.indexOf(self.$(activeElementSelector), activeElement);
                    // XXX for IE compatibility, this might work:
                    // http://the-stickman.com/web-development/javascript/ ...
                    // finding-selection-cursor-position-in-a-textarea-in-internet-explorer/
                    // Only try to get the selectionStart and selectionEnd for inputs that have
                    // text in them.  I don't know if submit/button types are the only ones that
                    // will fail here, so maybe it'd just be either to wrap this in a try/catch.
                    var inputType = activeElement.getAttribute('type');
                    if (inputType !== 'submit' && inputType !== 'button') {
                        selectionStart = activeElement.selectionStart;
                        selectionEnd = activeElement.selectionEnd;
                    }
                }

                var $old = $('<div>').append(this.$el.children());
                var newHtml = renderTemplate(self.templateId, self);
                log(INFO, self, 'newhtml', newHtml);
                self.$el.html(newHtml);

                /**
                 * Execute the "fragment ready" callback.
                 */
                self['ready']();
                self['postReady']();

                /**
                 * (Re-)create sub-views for each descendent element with a tbone attribute.
                 * On re-renders, the pre-existing list of sub-views is passed to render, which
                 * attempts to pair already-rendered views with matching elements in this view's
                 * newly re-rendered template.  Matching views are transferred to the new DOM
                 * hierarchy without disruption.
                 */
                var oldSubViews = self.subViews || [];
                self.subViews = render(self.$('[tbone]'), self, oldSubViews);
                var obsoleteSubViews = _.difference(oldSubViews, self.subViews);
                /**
                 * Destroy all of the sub-views that were not reused.
                 */
                _.each(obsoleteSubViews, function (view) {
                    view.destroy(self);
                });
                /**
                 * Call destroyDOM with the the pseudo-parent created above.  This DOM fragment contains all
                 * of the previously-rendered (if any) DOM structure of this view and subviews, minus any
                 * subviews that are being reused (which have already been moved to the new parent).
                 */
                self['destroyDOM']($old);

                /**
                 * If we saved it above, restore the active element focus and selection.
                 */
                if (activeElementSelector) {
                    var newActiveElement = self.$(activeElementSelector)[activeElementIndex];
                    if (newActiveElement) {
                        $(newActiveElement).focus();
                        if (selectionStart != null && selectionEnd != null) {
                            newActiveElement.selectionStart = selectionStart;
                            newActiveElement.selectionEnd = selectionEnd;
                        }
                    }
                }
            } else {
                self['ready']();
                self['postReady']();
            }
            self['postRender']();
            viewRenders++;
        }
    },

    /**
     * View.ready
     *
     * The "template-ready" callback.  This is the restricted tbone equivalent of document-ready.
     * It is the recommended means of adding interactivity/data/whatever to Views.
     *
     * At the moment this callback is executed, subviews are neither rendered nor are they
     * attached to the DOM fragment.  If you need to interact with subviews, use postRender.
     */
    'ready': noop,

    /**
     * View.postReady
     *
     * This is the same as ready, except that it executes after ready.  The typical use case is
     * to override this in your base template to provide automatic application-wide helpers,
     * such as activating a tooltip library, and to use View.ready for specific view logic.
     */
    'postReady': noop,

    /**
     * View.postRender
     *
     * The "fragment-updated" callback.  This is executed whenever this view is re-rendered,
     * and after all sub-views (recursively) have rendered.
     *
     * Note that because we optimistically re-use sub-views, this may be called multiple times
     * with the same sub-view DOM fragments.  Ensure that anything you do to DOM elements in
     * sub-views is idempotent.
     */
    'postRender': noop,

    /**
     * View.destroyDOM
     *
     * The "document-destroy" callback.  Use this to do cleanup on removal of old HTML, e.g.
     * destroying associated tooltips.
     *
     * Note: Destroy contents of the $el parameter, not this.$el!  (XXX make this less error-prone)
     *
     * @param  {!jQuery} $el jQuery selection of DOM fragment to destroy
     */
    'destroyDOM': function ($el) { },

    /**
     * If a root attribute was specified, use that as the root object for this view's
     * render, both in templating automatically as well as available via this.root in
     * `ready` and `postRender` callbacks.
     */
    root: function () {
        return this['query'](DONT_GET_DATA);
    },

    /**
     * Perform a query relative to the view's rootObj and rootStr, delegating to
     * rootObj for the actual query but prepending rootStr to the prop string.
     **/
    'query': function (flag, prop, value) {
        var isSet = false;
        if (typeof flag !== 'number') {
            /**
             * If no flag provided, shift the prop and value over.  We do it this way instead
             * of having flag last so that we can type-check flag and discern optional flags
             * from optional values.  And flag should only be used internally, anyway.
             */
            value = prop;
            prop = flag;
            flag = 0;
            /**
             * Use arguments.length to switch to set mode in order to properly support
             * setting undefined.
             */
            isSet = arguments.length === 2;
        }
        prop = (this.rootStr ? this.rootStr + '.' : '') + (prop || '');
        return isSet ? this.rootObj(flag, prop, value) : this.rootObj(flag, prop);
    },

    'parentRoot': function () {
        return this.domParentView && this.domParentView.root();
    },

    /**
     * Get the DOM parent view, i.e. the view associated with the closest
     * ancestor DOM node that is a view root element.
     */
    'parent': function () {
        return this.domParentView;
    },

    // These are used at template render.  They're really not properties of views so much
    // as it is useful to reference these functions on the view, which is what we pass to
    // _.template already.
    'getHashId': getHashId,
    'isQueryable': isQueryable,
    'denullText': denullText

};

var defaultView = baseView;
/**
 * Set the default View to use when rendering templates with no matching View.
 * @param {ViewPrototype} view
 */
function setDefaultView(view) {
    defaultView = view;
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import RaisedButton from 'material-ui/RaisedButton';

import './editor.css';
import { updateBlock } from '../redux/page';
import StageEditor from '../components/StageEditor';
import TeaserEditor from '../components/TeaserEditor';

const editors = {
  StageEditor,
  TeaserEditor,
};

const typeToComponent = (type) => {
  const name = type.substr(0, 1).toUpperCase() + type.substr(1) + 'Editor';
  if (!(name in editors)) {
    console.error(`Unable to find editor component for type "${type}"!`);
    return null;
  }
  return editors[name];
};

const savePage = (page) => {
  const path = window.location.pathname;
  const options = {
    method: 'post',
    headers: {
      'content-type': 'application/json',
    },
    body: JSON.stringify(page),
  };
  fetch(`/api${path}`, options)
    .then(() => console.log('Ok'))
    .catch(err => console.error(`Unable to save content for "${path}".`, err))
};

const Editor = ({ page, update }) => (

      {
        page.children && page.children.map(({ type, id, props }) => {
          const EditorComponent = typeToComponent(type);
          if (!EditorComponent) {
            return null;
          }

          return React.createElement(
            EditorComponent,
            { ...props, update, id, key: id }
          );
        })
      }

      <RaisedButton
        label="Save"
        onTouchTap={() => savePage(page)}
        primary
        fullWidth
      />

);

Editor.propTypes = {
  page: PropTypes.shape({
    blocks: PropTypes.arrayOf(
      PropTypes.shape({
        type: PropTypes.string,
        props: React.object,
      })
    ),
  }),
  update: PropTypes.func,
};

export default connect(
  (state) => ({ page: state }),
  { update: updateBlock }
)(Editor);
<commit_msg>Sanitize page children before saving<commit_after>import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import RaisedButton from 'material-ui/RaisedButton';

import './editor.css';
import { updateBlock } from '../redux/page';
import StageEditor from '../components/StageEditor';
import TeaserEditor from '../components/TeaserEditor';

const editors = {
  StageEditor,
  TeaserEditor,
};

const typeToComponent = (type) => {
  const name = type.substr(0, 1).toUpperCase() + type.substr(1) + 'Editor';
  if (!(name in editors)) {
    console.error(`Unable to find editor component for type "${type}"!`);
    return null;
  }
  return editors[name];
};

const sanitizePage = (page) => ({
  ...page,
  children: page.children.map(child => ({
    type: child.type,
    props: child.props,
  })),
});

const savePage = (page) => {
  const path = window.location.pathname;
  const options = {
    method: 'post',
    headers: {
      'content-type': 'application/json',
    },
    body: JSON.stringify(sanitizePage(page)),
  };
  fetch(`/api${path}`, options)
    .then(() => console.log('Ok'))
    .catch(err => console.error(`Unable to save content for "${path}".`, err))
};

const Editor = ({ page, update }) => (

      {
        page.children && page.children.map(({ type, id, props }) => {
          const EditorComponent = typeToComponent(type);
          if (!EditorComponent) {
            return null;
          }

          return React.createElement(
            EditorComponent,
            { ...props, update, id, key: id }
          );
        })
      }

      <RaisedButton
        label="Save"
        onTouchTap={() => savePage(page)}
        primary
        fullWidth
      />

);

Editor.propTypes = {
  page: PropTypes.shape({
    blocks: PropTypes.arrayOf(
      PropTypes.shape({
        type: PropTypes.string,
        props: React.object,
      })
    ),
  }),
  update: PropTypes.func,
};

export default connect(
  (state) => ({ page: state }),
  { update: updateBlock }
)(Editor);
<|endoftext|>


<commit_before>import utils from '../utils/utils'
import config from '../utils/config'

export default {
  dictMapping(value, key, connector) {
    let dict = config.getDict(key);
    if (!dict || utils.isNull(value)) return '';
    if (connector) {
      value = value.split(connector);
    }
    if (!utils.isNull(value) && value !== '' && key) {
      if (!utils.isArray(value)) {
        value = [value];
      }
    }
    if (value.length <= 0) {
      return '';
    }

    const keyField = config.getOption('dict', 'keyName');
    const titleField = config.getOption('dict', 'titleName');

    if (utils.isArray(dict)) {
      dict = utils.toObject(dict, keyField);
    }
    return value.map((ele) => {
      if (utils.isObject(ele)) {
        return ele[titleField];
      }
      const d = dict[ele];
      if (utils.isObject(d)) {
        return d[titleField];
      }
      return d;
    }).filter(ele => (ele && ele !== '')).join(', ');
  }
}
<commit_msg>fix: dictMapping 添加connector参数<commit_after>import utils from '../utils/utils'
import config from '../utils/config'

export default {
  dictMapping(value, key, connector = ', ') {
    let dict = config.getDict(key);
    if (!dict || utils.isNull(value)) return '';
    if (connector) {
      value = value.split(connector);
    }
    if (!utils.isNull(value) && value !== '' && key) {
      if (!utils.isArray(value)) {
        value = [value];
      }
    }
    if (value.length <= 0) {
      return '';
    }

    const keyField = config.getOption('dict', 'keyName');
    const titleField = config.getOption('dict', 'titleName');

    if (utils.isArray(dict)) {
      dict = utils.toObject(dict, keyField);
    }
    return value.map((ele) => {
      if (utils.isObject(ele)) {
        return ele[titleField];
      }
      const d = dict[ele];
      if (utils.isObject(d)) {
        return d[titleField];
      }
      return d;
    }).filter(ele => (ele && ele !== '')).join(connector);
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import cx from 'classnames';
import pick from 'lodash/object/pick';
import omit from 'lodash/object/omit';
import { props, t } from '../utils';

const PropTypes = {
  /**
   * FlexView content
   */
  children: t.ReactChildren,
  /**
   * flex-direction: column
   */
  column: t.maybe(t.Boolean),
  /**
   * align content vertically
   */
  vAlignContent: t.maybe(t.enums.of(['top', 'center', 'bottom'])),
  /**
   * align content horizontally
   */
  hAlignContent: t.maybe(t.enums.of(['left', 'center', 'right'])),
  /**
   * margin-left property ("auto" to align self right)
   */
  marginLeft: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * margin-top property ("auto" to align self bottom)
   */
  marginTop: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * margin-right property ("auto" to align self left)
   */
  marginRight: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * margin-bottom property ("auto" to align self top)
   */
  marginBottom: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * flex-grow property (for parent primary axis)
   */
  grow: t.maybe(t.union([ t.Boolean, t.Number ])),
  /**
   * flex-shrink property
   */
  shrink: t.maybe(t.union([ t.Boolean, t.Number ])),
  /**
   * flex-basis property
   */
  basis: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * wrap content
   */
  wrap: t.maybe(t.Boolean),
  /**
   * height property (for parent secondary axis)
   */
  height: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * width property (for parent secondary axis)
   */
  width: t.maybe(t.union([ t.String, t.Number ])),
  className: t.maybe(t.String),
  style: t.maybe(t.Object)
};
@props(PropTypes, { strict: false })
export default class FlexView extends React.Component {

  getGrow = () => {
    const { grow } = this.props;
    if (typeof grow === 'number') {
      return grow;
    } else if (grow) {
      return 1;
    } else {
      return 0; // auto === true or default
    }
  };

  getShrink = () => {
    const { shrink, basis } = this.props;
    if (typeof shrink === 'number') {
      return shrink;
    } else if (shrink) {
      return 1;
    } else if (shrink === false) {
      return 0;
    }

    if (basis) {
      return 0;
    }

    return 1; // default
  };

  getBasis = () => {
    const { grow, shrink, basis } = this.props;
    if (basis) {
      const suffix = t.Number.is(basis) ? 'px' : '';
      return basis + suffix;
    } else if (grow && !shrink) {
      return '100%';
    }

    return 'auto'; // default
  };

  getFlexStyle = () => {
    const grow = this.getGrow();
    const shrink = this.getShrink();
    const basis = this.getBasis();
    const values = `${grow} ${shrink} ${basis}`;
    return {
      WebkitBoxFlex: values,
      MozBoxFlex: values,
      msFlex: values,
      WebkitFlex: values,
      flex: values
    };
  };

  getStyle = () => {
    const style = pick(this.props, [
      'width',
      'height',
      'marginLeft',
      'marginTop',
      'marginRight',
      'marginBottom'
    ]);
    return { ...this.getFlexStyle(), ...style, ...this.props.style };
  };

  getContentAlignmentClasses = () => {
    const vPrefix = this.props.column ? 'justify-content-' : 'align-content-';
    const hPrefix = this.props.column ? 'align-content-' : 'justify-content-';

    const vAlignContentClasses = {
      top: `${vPrefix}start`,
      center: `${vPrefix}center`,
      bottom: `${vPrefix}end`
    };

    const hAlignContentClasses = {
      left: `${hPrefix}start`,
      center: `${hPrefix}center`,
      right: `${hPrefix}end`
    };

    const vAlignContent = vAlignContentClasses[this.props.vAlignContent];
    const hAlignContent = hAlignContentClasses[this.props.hAlignContent];

    return cx(vAlignContent, hAlignContent);
  };

  getClasses = () => {
    const direction = this.props.column ? 'flex-column' : 'flex-row';
    const contentAlignment = this.getContentAlignmentClasses();
    const wrap = this.props.wrap && 'flex-wrap';
    return cx('react-flex-view', direction, contentAlignment, wrap, this.props.className);
  };

  render() {
    const className = this.getClasses();
    const style = this.getStyle();
    const props = omit(this.props, Object.keys(PropTypes));
    return (

        {this.props.children}

    );
  }

}
<commit_msg>code clean<commit_after>import React from 'react';
import cx from 'classnames';
import pick from 'lodash/object/pick';
import omit from 'lodash/object/omit';
import { props, t } from '../utils';

const PropTypes = {
  /**
   * FlexView content
   */
  children: t.ReactChildren,
  /**
   * flex-direction: column
   */
  column: t.maybe(t.Boolean),
  /**
   * align content vertically
   */
  vAlignContent: t.maybe(t.enums.of(['top', 'center', 'bottom'])),
  /**
   * align content horizontally
   */
  hAlignContent: t.maybe(t.enums.of(['left', 'center', 'right'])),
  /**
   * margin-left property ("auto" to align self right)
   */
  marginLeft: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * margin-top property ("auto" to align self bottom)
   */
  marginTop: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * margin-right property ("auto" to align self left)
   */
  marginRight: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * margin-bottom property ("auto" to align self top)
   */
  marginBottom: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * flex-grow property (for parent primary axis)
   */
  grow: t.maybe(t.union([ t.Boolean, t.Number ])),
  /**
   * flex-shrink property
   */
  shrink: t.maybe(t.union([ t.Boolean, t.Number ])),
  /**
   * flex-basis property
   */
  basis: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * wrap content
   */
  wrap: t.maybe(t.Boolean),
  /**
   * height property (for parent secondary axis)
   */
  height: t.maybe(t.union([ t.String, t.Number ])),
  /**
   * width property (for parent secondary axis)
   */
  width: t.maybe(t.union([ t.String, t.Number ])),
  className: t.maybe(t.String),
  style: t.maybe(t.Object)
};
@props(PropTypes, { strict: false })
export default class FlexView extends React.Component {

  getGrow = () => {
    const { grow } = this.props;
    if (typeof grow === 'number') {
      return grow;
    } else if (grow) {
      return 1;
    }

    return 0; // default
  };

  getShrink = () => {
    const { shrink, basis } = this.props;
    if (typeof shrink === 'number') {
      return shrink;
    } else if (shrink) {
      return 1;
    } else if (shrink === false) {
      return 0;
    }

    if (basis) {
      return 0;
    }

    return 1; // default
  };

  getBasis = () => {
    const { grow, shrink, basis } = this.props;
    if (basis) {
      const suffix = t.Number.is(basis) ? 'px' : '';
      return basis + suffix;
    } else if (grow && !shrink) {
      return '100%';
    }

    return 'auto'; // default
  };

  getFlexStyle = () => {
    const grow = this.getGrow();
    const shrink = this.getShrink();
    const basis = this.getBasis();
    const values = `${grow} ${shrink} ${basis}`;
    return {
      WebkitBoxFlex: values,
      MozBoxFlex: values,
      msFlex: values,
      WebkitFlex: values,
      flex: values
    };
  };

  getStyle = () => {
    const style = pick(this.props, [
      'width',
      'height',
      'marginLeft',
      'marginTop',
      'marginRight',
      'marginBottom'
    ]);
    return { ...this.getFlexStyle(), ...style, ...this.props.style };
  };

  getContentAlignmentClasses = () => {
    const vPrefix = this.props.column ? 'justify-content-' : 'align-content-';
    const hPrefix = this.props.column ? 'align-content-' : 'justify-content-';

    const vAlignContentClasses = {
      top: `${vPrefix}start`,
      center: `${vPrefix}center`,
      bottom: `${vPrefix}end`
    };

    const hAlignContentClasses = {
      left: `${hPrefix}start`,
      center: `${hPrefix}center`,
      right: `${hPrefix}end`
    };

    const vAlignContent = vAlignContentClasses[this.props.vAlignContent];
    const hAlignContent = hAlignContentClasses[this.props.hAlignContent];

    return cx(vAlignContent, hAlignContent);
  };

  getClasses = () => {
    const direction = this.props.column ? 'flex-column' : 'flex-row';
    const contentAlignment = this.getContentAlignmentClasses();
    const wrap = this.props.wrap && 'flex-wrap';
    return cx('react-flex-view', direction, contentAlignment, wrap, this.props.className);
  };

  render() {
    const className = this.getClasses();
    const style = this.getStyle();
    const props = omit(this.props, Object.keys(PropTypes));
    return (

        {this.props.children}

    );
  }

}
<|endoftext|>