<commit_before>/**
 * @overview ccm component for user authentication
 * @author André Kless <andre.kless@web.de> 2016-2017
 * @license The MIT License (MIT)
 * @version latest (1.0.0)
 */

( function () {

  var ccm_version = '9.0.0';
  var ccm_url     = 'https://akless.github.io/ccm/ccm.min.js';

  var component_name = 'kanban_board';
  var component_obj  = {

    name: component_name,

    config: {

      html_templates: {},
      data: {
        store: [ 'ccm.store', { store: 'kanban', url: 'wss://ccm.inf.h-brs.de' } ],
        key: 'demo'
      },
      icons:    [ 'ccm.load', 'https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css', { url: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css', context: document.head } ],
      //style:    [ 'ccm.load',  'https://akless.github.io/ccm-components/kanban_board/layout/default.css' ]

      //html:     [ ccm.load,  './json/kanban_html.json' ],
      //key:      'demo',
      //store:    [ ccm.store, { /*local: './json/kanban.json',*/ store: 'kanban', url: 'wss://ccm.inf.h-brs.de/index.js' } ],

      //ui_sort:  [ 'ccm.load',  './lib/jquery-ui/sortable/jquery-ui.min.js' ]

    },

    Instance: function () {

      var self = this;
      var my;           // contains privatized instance members

      this.init = function ( callback ) {

        // listen to datastore change event => update own content
        self.data.store.onChange = function () { self.start(); };

        callback();
      };

      this.ready = function ( callback ) {

        // privatize all possible instance members
        my = self.ccm.helper.privatize( self );

        callback();
      };

      this.start = function ( callback ) {

        // get dataset for rendering
        self.ccm.helper.dataset( my.data, function ( dataset ) {
          console.log( dataset );
          return;

          // render main html structure
          element.html( ccm.helper.html( self.html.main, {

            title:       ccm.helper.val( self.title       || dataset.title       ),
            description: ccm.helper.val( self.description || dataset.description )

          } ) );

          if ( !( self.title       || dataset.title       ) ) ccm.helper.find( self, '.title'       ).remove();
          if ( !( self.description || dataset.description ) ) ccm.helper.find( self, '.description' ).remove();

          if ( !dataset.members     ) dataset.members    = [ 'Adam', 'Eva', 'Romeo', 'Julia'  ];
          if ( !dataset.priorities  ) dataset.priorities = [ 'A', 'B', 'C' ];
          if ( !dataset.lanes       ) dataset.lanes      = [ 'ToDo', 'Doing', 'Done' ];

          var lanes_div = ccm.helper.find( self, '.lanes' );

          for ( var i = 0; i < dataset.lanes.length; i++ )
            renderLane( i );

          sortable();

          // translate own content
          if ( self.lang ) self.lang.render();

          // perform callback
          if ( callback ) callback();

          function renderLane( i ) {

            if ( typeof dataset.lanes[ i ] === 'string' ) dataset.lanes[ i ] = { title: dataset.lanes[ i ] };
            if ( !dataset.lanes[ i ].tasks ) dataset.lanes[ i ].tasks = i === 0 ? [ {} ] : [];

            lanes_div.append( ccm.helper.html( self.html.lane, { title: ccm.helper.val( dataset.lanes[ i ].title ) } ) );

            var lane_div  = ccm.helper.find( self, lanes_div, '.lane:last' );
            var tasks_div = ccm.helper.find( self, lane_div,  '.tasks'     );

            for ( var j = 0; j < dataset.lanes[ i ].tasks.length; j++ )
              renderTask( tasks_div, i, j );

          }

          function renderTask( tasks_div, i, j ) {

            var lane_div, task_div, entry_div, value_div, value, property;

            tasks_div.append( ccm.helper.html( self.html.task, {

              title:         ccm.helper.val( dataset.lanes[ i ].tasks[ j ].title    ).replace( /_quot_/g, """ ),
              owner:         ccm.helper.val( dataset.lanes[ i ].tasks[ j ].owner    ).replace( /_quot_/g, """ ),
              summary:       ccm.helper.val( dataset.lanes[ i ].tasks[ j ].summary  ).replace( /_quot_/g, """ ),
              priority:      ccm.helper.val( dataset.lanes[ i ].tasks[ j ].priority ).replace( /_quot_/g, """ ),
              deadline:      ccm.helper.val( dataset.lanes[ i ].tasks[ j ].deadline ).replace( /_quot_/g, """ ),

              inputTitle:    inputTitle,
              clickOwner:    clickOwner,
              inputSummary:  inputSummary,
              clickPriority: clickPriority,
              clickDeadline: clickDeadline

            } ) );

            task_div = ccm.helper.find( self, tasks_div, '.task:last' );

            ccm.helper.find( self, task_div, '.value' ).each( function () {

              value_div = jQuery( this );
              entry_div = value_div.closest( '.entry' );
              value = jQuery.trim( value_div.html() );
              empty();

            } );

            function inputTitle() {

              refresh( jQuery( this ), 'title' );
              editable();

            }

            function clickOwner() {

              refresh( jQuery( this ), 'owner' );
              select( 'members', clickOwner );

            }

            function inputSummary() {

              refresh( jQuery( this ), 'summary' );
              editable();

            }

            function clickPriority() {

              refresh( jQuery( this ), 'priority' );
              select( 'priorities', clickPriority );

            }

            function clickDeadline() {

              refresh( jQuery( this ), 'deadline' );
              input( 'date', clickDeadline );

            }

            function refresh( div, prop ) {

              lane_div  = div.closest( '.lane' );
              task_div  = div.closest( '.task' );
              entry_div = div.closest( '.entry' );
              value_div = div;
              value     = jQuery.trim( value_div.is( 'input' ) ? value_div.val() : value_div.html() );
              property  = prop;

              i = ccm.helper.find( self, '.lane' ).index( div.closest( '.lane' ) );
              j = ccm.helper.find( self, lane_div, '.task' ).index( task_div );

            }

            function editable() {

              empty();
              add_del( task_div );
              update();

            }

            function select( values, click ) {

              var temp = entry_div.html();
              var entries = [ { tag: 'option' } ];
              var entry_value;

              for ( var k = 0; k < dataset[ values ].length; k++ ) {

                entry_value = dataset[ values ][ k ];
                entries.push( { tag: 'option', value: entry_value, inner: entry_value, selected: entry_value === dataset.lanes[ i ].tasks[ j ][ property ] } );

              }

              entry_div.html( ccm.helper.html( { tag: 'select', inner: entries, onchange: onChange, onblur: onBlur } ) );

              ccm.helper.find( self, entry_div, 'select' ).focus();

              function onChange() {

                value = jQuery.trim( jQuery( this ).val() );
                restore( temp, click );
                update();

              }

              function onBlur() {

                restore( temp, click );

              }

            }

            function input( type, click ) {

              var temp = entry_div.html();

              entry_div.html( ccm.helper.html( { tag: 'input', type: type, value: dataset.lanes[ i ].tasks[ j ][ property ], oninput: onInput, onblur: onBlur } ) );

              ccm.helper.find( self, entry_div, 'input' ).focus();

              function onInput() {

                value_div = jQuery( this );
                value = jQuery.trim( value_div.val() );
                update();

              }

              function onBlur() {

                restore( temp, click );

              }

            }

            function restore( temp, click ) {

              entry_div.html( temp );
              value_div = ccm.helper.find( self, entry_div, '.value' ).click( click );

              if ( value !== undefined ) {

                value_div.html( value );
                empty();
                add_del( task_div );

              }

            }

            function empty() {

              if ( value === '' || value === '
' ) {
                entry_div.addClass( 'empty' );
                value_div.html( '' );
              }
              else
                entry_div.removeClass( 'empty' );

            }

            function update() {

              var status_div = ccm.helper.find( self, task_div, '.status' ).stop().html( '' ).fadeTo( 0, 1 );
              ccm.helper.loading( status_div );
              refresh( value_div, property );
              if ( i >= 0 && j >= 0 ) dataset.lanes[ i ].tasks[ j ][ property ] = ccm.helper.val( value ).replace( /"/g, '_quot_' );
              self.store.set( dataset, function () { status_div.fadeOut(); } );

            }

          }

          function sortable() {

            var start_lane;
            var start_task;
            var end_lane;
            var end_task;

            ccm.helper.find( self, '.tasks' ).sortable( {

              connectWith: ccm.helper.find( self, '.tasks' ),
              forcePlaceholderSize: true,
              placeholder: 'placeholder',
              cancel: '.task > div *',
              start: function ( event, ui ) {

                start_lane = ccm.helper.find( self, '.lane' ).index( ui.item.parent().parent() );
                start_task = ui.item.index();

              },
              update: function ( event, ui ) {

                if ( ui.sender !== null ) return;

                var div = ui.item;
                var task = dataset.lanes[ start_lane ].tasks[ start_task ];

                end_lane = ccm.helper.find( self, '.lane' ).index( div.parent().parent() );
                end_task = div.index();

                dataset.lanes[ start_lane ].tasks.splice( start_task, 1 );
                dataset.lanes[ end_lane ].tasks.splice( end_task, 0, task );

                add_del();

                div = ccm.helper.find( self, div, '.status' ).stop().html( '' ).fadeTo( 0, 1 );
                ccm.helper.loading( div );
                self.store.set( dataset, function () { div.fadeOut(); } );

              }

            } );

          }

          function add_del( task_div ) {

            var max_empties = 5;

            if ( task_div ) del( task_div ); else ccm.helper.find( self, '.task' ).each( function () { del( jQuery( this ) ); } );

            add();

            function add() {

              var task_div = ccm.helper.find( self, '.lane:first .task:last' );

              if ( ccm.helper.find( self, task_div, '.empty' ).length < max_empties ) {

                dataset.lanes[ 0 ].tasks.push( {} );
                renderTask( ccm.helper.find( self, '.lane:first .tasks' ), 0, dataset.lanes[ 0 ].tasks.length - 1 );

              }

            }

            function del( task_div ) {

              if ( ccm.helper.find( self, task_div, '.empty' ).length < max_empties ) return;

              var i = ccm.helper.find( self, '.lane' ).index( task_div.parent().parent() );
              var j = task_div.index();

              if ( i === 0 && j === dataset.lanes[ 0 ].tasks[ dataset.lanes[ 0 ].tasks.length - 1 ] ) return;

              task_div.remove();
              dataset.lanes[ i ].tasks.splice( j, 1 );

            }

          }

        } );

      };

    }

  };

  var namespace = window.ccm && ccm.components[ component_name ]; if ( namespace ) { if ( namespace.ccm_version ) ccm_version = namespace.ccm_version; if ( namespace.ccm_url ) ccm_url = namespace.ccm_url; }
  if ( !window.ccm || !ccm[ ccm_version ] ) { var tag = document.createElement( 'script' ); document.head.appendChild( tag ); tag.onload = register; tag.src = ccm_url; } else register();
  function register() { ccm[ ccm_version ].component( component_obj ); }
}() );<commit_msg>update kanban board component<commit_after>/**
 * @overview ccm component for user authentication
 * @author André Kless <andre.kless@web.de> 2016-2017
 * @license The MIT License (MIT)
 * @version latest (1.0.0)
 */

( function () {

  var ccm_version = '9.0.0';
  var ccm_url     = 'https://akless.github.io/ccm/ccm.min.js';

  var component_name = 'kanban_board';
  var component_obj  = {

    name: component_name,

    config: {

      html_templates: {},
      data: {
        store: [ 'ccm.store', { store: 'kanban', url: 'wss://ccm.inf.h-brs.de' } ],
        key: 'demo'
      },
      icons: [ 'ccm.load', 'https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css', { url: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css', context: document.head } ]
      //style:    [ 'ccm.load',  'https://akless.github.io/ccm-components/kanban_board/layout/default.css' ]

      //html:     [ ccm.load,  './json/kanban_html.json' ],
      //key:      'demo',
      //store:    [ ccm.store, { /*local: './json/kanban.json',*/ store: 'kanban', url: 'wss://ccm.inf.h-brs.de/index.js' } ],

      //ui_sort:  [ 'ccm.load',  './lib/jquery-ui/sortable/jquery-ui.min.js' ]

    },

    Instance: function () {

      var self = this;
      var my;           // contains privatized instance members

      this.init = function ( callback ) {

        // listen to datastore change event => update own content
        self.data.store.onChange = function () { self.start(); };

        callback();
      };

      this.ready = function ( callback ) {

        // privatize all possible instance members
        my = self.ccm.helper.privatize( self );

        callback();
      };

      this.start = function ( callback ) {

        // get dataset for rendering
        self.ccm.helper.dataset( my.data, function ( dataset ) {
          console.log( dataset );
          return;

          // render main html structure
          element.html( ccm.helper.html( self.html.main, {

            title:       ccm.helper.val( self.title       || dataset.title       ),
            description: ccm.helper.val( self.description || dataset.description )

          } ) );

          if ( !( self.title       || dataset.title       ) ) ccm.helper.find( self, '.title'       ).remove();
          if ( !( self.description || dataset.description ) ) ccm.helper.find( self, '.description' ).remove();

          if ( !dataset.members     ) dataset.members    = [ 'Adam', 'Eva', 'Romeo', 'Julia'  ];
          if ( !dataset.priorities  ) dataset.priorities = [ 'A', 'B', 'C' ];
          if ( !dataset.lanes       ) dataset.lanes      = [ 'ToDo', 'Doing', 'Done' ];

          var lanes_div = ccm.helper.find( self, '.lanes' );

          for ( var i = 0; i < dataset.lanes.length; i++ )
            renderLane( i );

          sortable();

          // translate own content
          if ( self.lang ) self.lang.render();

          // perform callback
          if ( callback ) callback();

          function renderLane( i ) {

            if ( typeof dataset.lanes[ i ] === 'string' ) dataset.lanes[ i ] = { title: dataset.lanes[ i ] };
            if ( !dataset.lanes[ i ].tasks ) dataset.lanes[ i ].tasks = i === 0 ? [ {} ] : [];

            lanes_div.append( ccm.helper.html( self.html.lane, { title: ccm.helper.val( dataset.lanes[ i ].title ) } ) );

            var lane_div  = ccm.helper.find( self, lanes_div, '.lane:last' );
            var tasks_div = ccm.helper.find( self, lane_div,  '.tasks'     );

            for ( var j = 0; j < dataset.lanes[ i ].tasks.length; j++ )
              renderTask( tasks_div, i, j );

          }

          function renderTask( tasks_div, i, j ) {

            var lane_div, task_div, entry_div, value_div, value, property;

            tasks_div.append( ccm.helper.html( self.html.task, {

              title:         ccm.helper.val( dataset.lanes[ i ].tasks[ j ].title    ).replace( /_quot_/g, """ ),
              owner:         ccm.helper.val( dataset.lanes[ i ].tasks[ j ].owner    ).replace( /_quot_/g, """ ),
              summary:       ccm.helper.val( dataset.lanes[ i ].tasks[ j ].summary  ).replace( /_quot_/g, """ ),
              priority:      ccm.helper.val( dataset.lanes[ i ].tasks[ j ].priority ).replace( /_quot_/g, """ ),
              deadline:      ccm.helper.val( dataset.lanes[ i ].tasks[ j ].deadline ).replace( /_quot_/g, """ ),

              inputTitle:    inputTitle,
              clickOwner:    clickOwner,
              inputSummary:  inputSummary,
              clickPriority: clickPriority,
              clickDeadline: clickDeadline

            } ) );

            task_div = ccm.helper.find( self, tasks_div, '.task:last' );

            ccm.helper.find( self, task_div, '.value' ).each( function () {

              value_div = jQuery( this );
              entry_div = value_div.closest( '.entry' );
              value = jQuery.trim( value_div.html() );
              empty();

            } );

            function inputTitle() {

              refresh( jQuery( this ), 'title' );
              editable();

            }

            function clickOwner() {

              refresh( jQuery( this ), 'owner' );
              select( 'members', clickOwner );

            }

            function inputSummary() {

              refresh( jQuery( this ), 'summary' );
              editable();

            }

            function clickPriority() {

              refresh( jQuery( this ), 'priority' );
              select( 'priorities', clickPriority );

            }

            function clickDeadline() {

              refresh( jQuery( this ), 'deadline' );
              input( 'date', clickDeadline );

            }

            function refresh( div, prop ) {

              lane_div  = div.closest( '.lane' );
              task_div  = div.closest( '.task' );
              entry_div = div.closest( '.entry' );
              value_div = div;
              value     = jQuery.trim( value_div.is( 'input' ) ? value_div.val() : value_div.html() );
              property  = prop;

              i = ccm.helper.find( self, '.lane' ).index( div.closest( '.lane' ) );
              j = ccm.helper.find( self, lane_div, '.task' ).index( task_div );

            }

            function editable() {

              empty();
              add_del( task_div );
              update();

            }

            function select( values, click ) {

              var temp = entry_div.html();
              var entries = [ { tag: 'option' } ];
              var entry_value;

              for ( var k = 0; k < dataset[ values ].length; k++ ) {

                entry_value = dataset[ values ][ k ];
                entries.push( { tag: 'option', value: entry_value, inner: entry_value, selected: entry_value === dataset.lanes[ i ].tasks[ j ][ property ] } );

              }

              entry_div.html( ccm.helper.html( { tag: 'select', inner: entries, onchange: onChange, onblur: onBlur } ) );

              ccm.helper.find( self, entry_div, 'select' ).focus();

              function onChange() {

                value = jQuery.trim( jQuery( this ).val() );
                restore( temp, click );
                update();

              }

              function onBlur() {

                restore( temp, click );

              }

            }

            function input( type, click ) {

              var temp = entry_div.html();

              entry_div.html( ccm.helper.html( { tag: 'input', type: type, value: dataset.lanes[ i ].tasks[ j ][ property ], oninput: onInput, onblur: onBlur } ) );

              ccm.helper.find( self, entry_div, 'input' ).focus();

              function onInput() {

                value_div = jQuery( this );
                value = jQuery.trim( value_div.val() );
                update();

              }

              function onBlur() {

                restore( temp, click );

              }

            }

            function restore( temp, click ) {

              entry_div.html( temp );
              value_div = ccm.helper.find( self, entry_div, '.value' ).click( click );

              if ( value !== undefined ) {

                value_div.html( value );
                empty();
                add_del( task_div );

              }

            }

            function empty() {

              if ( value === '' || value === '
' ) {
                entry_div.addClass( 'empty' );
                value_div.html( '' );
              }
              else
                entry_div.removeClass( 'empty' );

            }

            function update() {

              var status_div = ccm.helper.find( self, task_div, '.status' ).stop().html( '' ).fadeTo( 0, 1 );
              ccm.helper.loading( status_div );
              refresh( value_div, property );
              if ( i >= 0 && j >= 0 ) dataset.lanes[ i ].tasks[ j ][ property ] = ccm.helper.val( value ).replace( /"/g, '_quot_' );
              self.store.set( dataset, function () { status_div.fadeOut(); } );

            }

          }

          function sortable() {

            var start_lane;
            var start_task;
            var end_lane;
            var end_task;

            ccm.helper.find( self, '.tasks' ).sortable( {

              connectWith: ccm.helper.find( self, '.tasks' ),
              forcePlaceholderSize: true,
              placeholder: 'placeholder',
              cancel: '.task > div *',
              start: function ( event, ui ) {

                start_lane = ccm.helper.find( self, '.lane' ).index( ui.item.parent().parent() );
                start_task = ui.item.index();

              },
              update: function ( event, ui ) {

                if ( ui.sender !== null ) return;

                var div = ui.item;
                var task = dataset.lanes[ start_lane ].tasks[ start_task ];

                end_lane = ccm.helper.find( self, '.lane' ).index( div.parent().parent() );
                end_task = div.index();

                dataset.lanes[ start_lane ].tasks.splice( start_task, 1 );
                dataset.lanes[ end_lane ].tasks.splice( end_task, 0, task );

                add_del();

                div = ccm.helper.find( self, div, '.status' ).stop().html( '' ).fadeTo( 0, 1 );
                ccm.helper.loading( div );
                self.store.set( dataset, function () { div.fadeOut(); } );

              }

            } );

          }

          function add_del( task_div ) {

            var max_empties = 5;

            if ( task_div ) del( task_div ); else ccm.helper.find( self, '.task' ).each( function () { del( jQuery( this ) ); } );

            add();

            function add() {

              var task_div = ccm.helper.find( self, '.lane:first .task:last' );

              if ( ccm.helper.find( self, task_div, '.empty' ).length < max_empties ) {

                dataset.lanes[ 0 ].tasks.push( {} );
                renderTask( ccm.helper.find( self, '.lane:first .tasks' ), 0, dataset.lanes[ 0 ].tasks.length - 1 );

              }

            }

            function del( task_div ) {

              if ( ccm.helper.find( self, task_div, '.empty' ).length < max_empties ) return;

              var i = ccm.helper.find( self, '.lane' ).index( task_div.parent().parent() );
              var j = task_div.index();

              if ( i === 0 && j === dataset.lanes[ 0 ].tasks[ dataset.lanes[ 0 ].tasks.length - 1 ] ) return;

              task_div.remove();
              dataset.lanes[ i ].tasks.splice( j, 1 );

            }

          }

        } );

      };

    }

  };

  var namespace = window.ccm && ccm.components[ component_name ]; if ( namespace ) { if ( namespace.ccm_version ) ccm_version = namespace.ccm_version; if ( namespace.ccm_url ) ccm_url = namespace.ccm_url; }
  if ( !window.ccm || !ccm[ ccm_version ] ) { var tag = document.createElement( 'script' ); document.head.appendChild( tag ); tag.onload = register; tag.src = ccm_url; } else register();
  function register() { ccm[ ccm_version ].component( component_obj ); }
}() );<|endoftext|>


<commit_before>gimel.module('imageTemplate').extend(function(moduleContent) {
    moduleContent.extend(function(GimelImage) {
        /**
         * Make an exact copy of an image
         * @return {GimelImage} this the new image
         */
        GimelImage.prototype.clone = function() {
            return new GimelImage(this.width, this.height, this.data);
        };

        /**
         * Copy image data from a (different) typed image
         * @param {GimelImage} image the given image
         * @return {GimelImage} this image
         */
        GimelImage.prototype.from = function(image) {
            var convertedImage = new GimelImage(image.width, image.height);
            var dataSrc = image.data;
            var dataDest = this.data;
            for (var t = 0, tt = image.length; t < tt; ++t) {
                dataDest[t] = dataSrc[t];
            }
            return this;
        };

        /**
         * Copy image data from a same typed image
         * @param {GimelImage} image the given image
         * @return {GimelImage} this image
         */
        GimelImage.prototype.set = function(image) {
            this.data.set(image.data);
            return this;
        };
    });
});

<commit_msg>+ fill method : fill in an image or a channel with a value<commit_after>gimel.module('imageTemplate').extend(function(moduleContent) {
    moduleContent.extend(function(GimelImage) {
        /**
         * Make an exact copy of an image
         * @return {GimelImage} this the new image
         */
        GimelImage.prototype.clone = function() {
            return new GimelImage(this.width, this.height, this.data);
        };

        /**
         * Gives a new image with the same structure (width, height, channels)
         * @return {GimelImage} this the new image
         */
        GimelImage.prototype.cloneStructure = function() {
            return new GimelImage(this.width, this.height);
        };

        /**
         * Copy image data from a (different) typed image
         * @param {GimelImage} image the given image
         * @return {GimelImage} this image
         */
        GimelImage.prototype.from = function(image) {
            var convertedImage = new GimelImage(image.width, image.height);
            var dataSrc = image.data;
            var dataDest = this.data;
            for (var t = 0, tt = image.length; t < tt; ++t) {
                dataDest[t] = dataSrc[t];
            }
            return this;
        };

        /**
         * Copy image data from a same typed image
         * @param {GimelImage} image the given image
         * @return {GimelImage} this image
         */
        GimelImage.prototype.set = function(image) {
            this.data.set(image.data);
            return this;
        };

        if (GimelImage.prototype.CHANNELS === 1) {
            /**
             * Fill in a channel with a value
             * @param {number} value the value to set to pixels
             * @return {GimelImage} this image
             */
            GimelImage.prototype.fill = function(value) {
                var dataDest = this.data;
                for (var t = 0, tt = this.length; t < tt; ++t) {
                    dataDest[t] = value;
                }
                return this;
            };
        } else if (GimelImage.prototype.CHANNELS === 4) {
            /**
             * Fill in an image with channel values
             * @param {number} ch0 the first channel value
             * @param {number} ch1 the second channel value
             * @param {number} ch2 the third channel value
             * @param {number} ch3 the fourth channel value
             * @return {GimelImage} this image
             */
            GimelImage.prototype.fill = function(ch0, ch1, ch2, ch3) {
                var dataDest = this.data;
                for (var t = 0, tt = this.length; t < tt; t += 4) {
                    dataDest[t] = ch0;
                    dataDest[t + 1] = ch1;
                    dataDest[t + 2] = ch2;
                    dataDest[t + 3] = ch3;
                }
                return this;
            };

            /**
             * Fill in a channel with a value
             * @param {integer} channel the channel index
             * @param {number} value the value to set to channel
             * @return {GimelImage} this image
             */
            GimelImage.prototype.fillChannel = function(channel, value) {
                var dataDest = this.data;
                for (var t = 0, tt = this.length; t < tt; t += 4) {
                    dataDest[t + channel] = value;
                }
                return this;
            };
        }

    });
});

<|endoftext|>


<commit_before>/******************************************************************************
*                                                                             *
* promfacts -- Jetpack feature for prometheus Facts                           *
*                                                                             *
* Copyright (C) 2009-2010 Jens Wille                                          *
*                                                                             *
* promfacts is free software; you can redistribute it and/or modify it under  *
* the terms of the GNU General Public License as published by the Free        *
* Software Foundation; either version 3 of the License, or (at your option)   *
* any later version.                                                          *
*                                                                             *
* promfacts is distributed in the hope that it will be useful, but WITHOUT    *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
* FITNESSFOR A PARTICULAR PURPOSE. See the GNU General Public License for     *
* more details.                                                               *
*                                                                             *
* You should have received a copy of the GNU General Public License along     *
* with promfacts. If not, see <http://www.gnu.org/licenses/>.                 *
*                                                                             *
* @title       prometheus Facts                                               *
* @description Jetpack feature for prometheus Facts                           *
* @author      Jens Wille <jens.wille@uni-koeln.de>                           *
* @license     GPL                                                            *
* @version     0.0.3                                                          *
* @url         http://wiki.github.com/blackwinter/jetpacks/promfacts          *
* @update      http://github.com/blackwinter/jetpacks/raw/master/promfacts.js *
*                                                                             *
******************************************************************************/

var Prom = function() {
  var prefs = Components.classes['@mozilla.org/preferences-service;1']
                        .getService(Components.interfaces.nsIPrefService)
                        .getBranch('jetpacks.promfacts.');

  var get_pref = function(key, deflt) {
    var type = prefs.getPrefType(key);
    return type === prefs.PREF_STRING ? prefs.getCharPref(key) : deflt;
  };

  var url  = 'http://prometheus.uni-koeln.de/pandora',
      lang = get_pref('lang', 'en'),
      tr   = {};

  if (lang === 'de') {
    tr.title = 'prometheus in Zahlen';
    tr.none  = 'Keine gefunden';
  }
  else {
    tr.title = 'prometheus Facts';
    tr.none  = 'None found';
  }

  return {
    icon:  url + '/favicon.ico',
    tr:    tr,
    facts: function(callback) {
      jQuery.getJSON(url + '/' + lang + '/pandora.json', function(data) {
        var facts = data.facts;
        if (jQuery.isArray(facts) && facts.length > 0) {
          callback(facts);
        }
      });
    }
  };
}();

jetpack.statusBar.append({
  html: '',
  width: 18,

  onReady: function(widget) {
    $('img', widget).css({ cursor: 'pointer' });

    $(widget).click(function() {
      Prom.facts(function(facts) {
        jetpack.notifications.show({
          title: Prom.tr.title, icon:  Prom.icon,
          body:  facts ? facts.join('
') : Prom.tr.none + ' :-('
        });
      });
    });
  }
});
<commit_msg>Display facts as table. (v0.0.4)<commit_after>/******************************************************************************
*                                                                             *
* promfacts -- Jetpack feature for prometheus Facts                           *
*                                                                             *
* Copyright (C) 2009-2010 Jens Wille                                          *
*                                                                             *
* promfacts is free software; you can redistribute it and/or modify it under  *
* the terms of the GNU General Public License as published by the Free        *
* Software Foundation; either version 3 of the License, or (at your option)   *
* any later version.                                                          *
*                                                                             *
* promfacts is distributed in the hope that it will be useful, but WITHOUT    *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
* FITNESSFOR A PARTICULAR PURPOSE. See the GNU General Public License for     *
* more details.                                                               *
*                                                                             *
* You should have received a copy of the GNU General Public License along     *
* with promfacts. If not, see <http://www.gnu.org/licenses/>.                 *
*                                                                             *
* @title       prometheus Facts                                               *
* @description Jetpack feature for prometheus Facts                           *
* @author      Jens Wille <jens.wille@uni-koeln.de>                           *
* @license     GPL                                                            *
* @version     0.0.4                                                          *
* @url         http://wiki.github.com/blackwinter/jetpacks/promfacts          *
* @update      http://github.com/blackwinter/jetpacks/raw/master/promfacts.js *
*                                                                             *
******************************************************************************/

var Prom = function() {
  var prefs = Components.classes['@mozilla.org/preferences-service;1']
                        .getService(Components.interfaces.nsIPrefService)
                        .getBranch('jetpacks.promfacts.');

  var get_pref = function(key, deflt) {
    var type = prefs.getPrefType(key);
    return type === prefs.PREF_STRING ? prefs.getCharPref(key) : deflt;
  };

  var url  = 'http://prometheus.uni-koeln.de/pandora',
      lang = get_pref('lang', 'en'), tr = {};

  if (lang === 'de') {
    tr.title = 'prometheus in Zahlen';
    tr.none  = 'Keine gefunden';
  }
  else {
    tr.title = 'prometheus Facts';
    tr.none  = 'None found';
  }

  return {
    icon:  url + '/favicon.ico', tr: tr,
    facts: function(callback) {
      jQuery.ajax({
        type:    'GET', dataType: 'json',
        url:     url + '/' + lang + '/pandora.json',
        error:   function(xhr, status) { callback(); },
        success: function(data) {
          var facts = data.facts;
          callback(jQuery.isArray(facts) && facts.length > 0 && facts);
        }
      });
    }
  };
}();

jetpack.statusBar.append({
  html:    '', width: 18,
  onReady: function(widget) {
    $('img', widget).css({ cursor: 'pointer' });

    $(widget).click(function() {
      Prom.facts(function(facts) {
        jetpack.notifications.show({
          title: Prom.tr.title, icon: Prom.icon,
          body:  facts ? '
' + jQuery.map(facts, function(i) {
            var cells = '$1		$2	';
            return '
' + i.replace(/(\S+)\s+(.+)/, cells) + '
';
          }).join('') + '
' : Prom.tr.none + ' :-('
        });
      });
    });
  }
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.demos',
  name: 'MDFieldGuide',
  extendsModel: 'foam.ui.DetailView',
  requires: [
    'foam.input.touch.GestureManager',
    'foam.input.touch.TouchManager',
    'foam.graphics.ActionButtonCView',
    'foam.ui.md.CheckboxView',
    'foam.ui.md.ChoiceRadioView',
    'foam.ui.md.FlatButton',
    'foam.ui.md.PopupChoiceView',
    'foam.ui.md.SharedStyles',
    'foam.ui.md.TextFieldView',
    'foam.ui.md.ToggleView',
  ],

  exports: [
    'gestureManager',
    'touchManager',
  ],

  models: [
    {
      name: 'GuideData',
      properties: [
        {
          name: 'textField',
          view: 'foam.ui.md.TextFieldView',
        },
        {
          name: 'onKeyField',
          view: 'foam.ui.md.TextFieldView',
        },
        {
          name: 'longString',
          view: 'foam.ui.md.TextFieldView',
        },
        {
          model_: 'foam.core.types.StringEnumProperty',
          name: 'choiceField',
          defaultValue: 'FIRST',
          choices: [
            ['FIRST', 'First option'],
            ['SECOND', 'Second option'],
            ['THIRD', 'Third option']
          ]
        },
        {
          model_: 'BooleanProperty',
          name: 'booleanField',
          label: 'I can be toggled on and off',
          defaultValue: false
        },
      ],
      actions: [
        {
          name: 'basicAction',
          label: 'Basic Actions',
          action: function() {
          }
        },
      ]
    }
  ],

  properties: [
    {
      name: 'data',
      factory: function() {
        return this.GuideData.create({
          textField: 'Some text'
        });
      }
    },
    {
      name: 'touchManager',
      factory: function() {
        return this.TouchManager.create();
      }
    },
    {
      name: 'gestureManager',
      factory: function() {
        return this.GestureManager.create();
      }
    },
  ],

  templates: [
    function CSS() {/*
      .field-guide {
        display: flex;
        flex-direction: column;
        font-size: 16px;
        height: 100%;
        width: 100%;
      }
      .guide-header {
        background-color: #3367d6;
        color: #fff;
        flex-shrink: 0;
        font-size: 200%;
        padding: 10px 20px;
      }
      .guide-body {
        flex-grow: 1;
        overflow-y: scroll;
        padding: 10px;
      }

      .section {
      }
      .subsection {
      }
      .example {
        border: 1px solid #e0e0e0;
        padding: 10px;
        margin: 10px 0;
      }
      .fixme {
        color: red;
      }
      .fixme-low {
        color: orange;
      }
    */},
    function textFieldHTML() {/*

Text Fields

          Text fields are based around <tt>foam.ui.md.TextFieldView</tt>.
          This model has many useful options. Most of these have following examples.

<tt>mode</tt>: Set <tt>'read-only'</tt> for a disabled, read-only text field.

<tt>onKeyMode</tt>: Defaults to false, where the <tt>data</tt> is only updated on <tt>blur</tt> or Enter key. Set <tt>true</tt> to update <tt>data</tt> on every keystroke, eg. for autocomplete.

<tt>floatingLabel</tt>: Defaults to <tt>true</tt>. Disable to use placeholder text instead of the MD floating label.

<tt>growable</tt>: Default <tt>false</tt>. Enable to make the text field grow as its contents get longer. Example use: a comment field.

<tt>displayHeight</tt>: The number of lines this field should occupy. Defaults to 1; set higher values to create a <tt><textarea></tt> instead. Ignored when <tt>growable: true</tt>

<tt>underline</tt>: Disable to hide the underline for the field.

<tt>clearAction</tt>: Set <tt>true</tt> to show an "X" icon for clearing.

<tt>darkBackground</tt>: Set <tt>true</tt> to invert colors: the underline will be white instead of grey and blue, the clear action will be white instead of grey.

Examples

Default

Pure defaults, <tt>onKeyMode: false</tt> and <tt>floatingLabel: true</tt>

            $$textField

Read-only version

            $$textField{ mode: 'read-only' }

<tt>onKeyMode</tt>

Editable

            $$onKeyField{ onKeyMode: true }

Read-only

            $$onKeyField{ mode: 'read-only', onKeyMode: true }

No floating label

Note the placeholder when the field is empty.

            $$textField{ floatingLabel: false }

Text areas

<tt>growable: true</tt>

            $$longString{ onKeyMode: true, growable: true }

3-line text area, with scrolling (<tt>displayHeight: 3</tt>)

            $$longString{ onKeyMode: true, displayHeight: 3, displayWidth: 20 }

    */},
    function buttonHTML() {/*

Buttons

          MD Buttons come in several flavours: raised, flat and floating action buttons.
          FOAM has built-in models for the former two.
          You can restyle a raised button into a floating action button easily.

Raised Buttons

FIX ME: We haven't needed a textual raised button.

Flat Buttons

<tt>foam.ui.md.FlatButton</tt>

          $$basicAction{ model_: 'foam.ui.md.FlatButton' }

Floating Action Buttons

So far, we've only used the one built into <tt>foam.ui.AppController</tt>.
        It's a <tt>foam.graphics.ActionButtonCView</tt> with a <tt>Flare</tt>
        and custom styling to make it floating and round.

FIX ME eventually

    */},
    function choicesHTML() {/*

Choices

There are a few flavours of choice views.

<tt>foam.ui.md.ChoiceMenuView</tt> is a pop-open menu.

<tt>foam.ui.md.ChoiceRadioView</tt> is a set of radio buttons.

        Both expect a <tt>choices</tt> property, either supplied on the property
        or passed directly to the view. This <tt>choices</tt> is an array of choices,
        in order, where the choices are either a string, or a <tt>[value, label]</tt>
        pair.

Popup menu

<tt>foam.ui.md.PopupChoiceView</tt>

          $$choiceField{ model_: 'foam.ui.md.PopupChoiceView' }

TODO Broken when <tt>overflow: scroll</tt> is used.

TODO Add underline and drop-down arrow.

TODO Use inline styling, but without jumpy resizing when the text changes.

Radio buttons

<tt>foam.ui.md.ChoiceRadioView</tt>

          $$choiceField{ model_: 'foam.ui.md.ChoiceRadioView' }

    */},
    function booleanHTML() {/*

Booleans

There are a few flavours of boolean views as well:

<tt>foam.ui.md.ToggleView</tt> is a MD switch view, ON OFF.

<tt>foam.ui.md.CheckboxView</tt> is a standard right-aligned MD checkbox.

Toggle switches

<tt>foam.ui.md.ToggleView</tt>

          $$booleanField{ model_: 'foam.ui.md.ToggleView' }

Checkboxes

<tt>foam.ui.md.CheckboxView</tt>

          $$booleanField{ model_: 'foam.ui.md.CheckboxView' }

    */},
    function toHTML() {/*
      <div id="<%= this.id %>" class="field-guide">

          FOAM Material Design Views

General Notes

              Use <tt>mode: 'read-only'</tt> to get read-only versions of most
              views.

          <% this.textFieldHTML(out); %>
          <% this.buttonHTML(out); %>
          <% this.choicesHTML(out); %>
          <% this.booleanHTML(out); %>

    */},
  ]
});
<commit_msg>Updated MDFieldGuide<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.demos',
  name: 'MDFieldGuide',
  extendsModel: 'foam.ui.DetailView',
  requires: [
    'foam.input.touch.GestureManager',
    'foam.input.touch.TouchManager',
    'foam.graphics.ActionButtonCView',
    'foam.ui.md.CheckboxView',
    'foam.ui.md.ChoiceRadioView',
    'foam.ui.md.FlatButton',
    'foam.ui.md.PopupChoiceView',
    'foam.ui.md.SharedStyles',
    'foam.ui.md.TextFieldView',
    'foam.ui.md.ToggleView',
  ],

  exports: [
    'gestureManager',
    'touchManager',
  ],

  models: [
    {
      name: 'GuideData',
      properties: [
        {
          name: 'textField',
          view: 'foam.ui.md.TextFieldView',
        },
        {
          name: 'onKeyField',
          view: 'foam.ui.md.TextFieldView',
        },
        {
          name: 'longString',
          view: 'foam.ui.md.TextFieldView',
        },
        {
          model_: 'foam.core.types.StringEnumProperty',
          name: 'choiceField',
          defaultValue: 'FIRST',
          choices: [
            ['FIRST', 'First option'],
            ['SECOND', 'Second option'],
            ['THIRD', 'Third option']
          ]
        },
        {
          model_: 'BooleanProperty',
          name: 'booleanField',
          label: 'I can be toggled on and off',
          defaultValue: false
        },
      ],
      actions: [
        {
          name: 'basicAction',
          label: 'Basic Actions',
          action: function() {
          }
        },
      ]
    }
  ],

  properties: [
    {
      name: 'data',
      factory: function() {
        return this.GuideData.create({
          textField: 'Some text'
        });
      }
    },
    {
      name: 'touchManager',
      factory: function() {
        return this.TouchManager.create();
      }
    },
    {
      name: 'gestureManager',
      factory: function() {
        return this.GestureManager.create();
      }
    },
  ],

  templates: [
    function CSS() {/*
      .field-guide {
        display: flex;
        flex-direction: column;
        font-size: 16px;
        height: 100%;
        width: 100%;
      }
      .guide-header {
        background-color: #3367d6;
        color: #fff;
        flex-shrink: 0;
        font-size: 200%;
        padding: 10px 20px;
      }
      .guide-body {
        flex-grow: 1;
        overflow-y: scroll;
        padding: 10px;
      }

      .section {
      }
      .subsection {
      }
      .example {
        border: 1px solid #e0e0e0;
        padding: 10px;
        margin: 10px 0;
      }
      .fixme {
        color: red;
      }
      .fixme-low {
        color: orange;
      }
    */},
    function textFieldHTML() {/*

Text Fields

          Text fields are based around <tt>foam.ui.md.TextFieldView</tt>.
          This model has many useful options. Most of these have following examples.

<tt>mode</tt>: Set <tt>'read-only'</tt> for a disabled, read-only text field.

<tt>onKeyMode</tt>: Defaults to false, where the <tt>data</tt> is only updated on <tt>blur</tt> or Enter key. Set <tt>true</tt> to update <tt>data</tt> on every keystroke, eg. for autocomplete.

<tt>floatingLabel</tt>: Defaults to <tt>true</tt>. Disable to use placeholder text instead of the MD floating label.

<tt>growable</tt>: Default <tt>false</tt>. Enable to make the text field grow as its contents get longer. Example use: a comment field.

<tt>displayHeight</tt>: The number of lines this field should occupy. Defaults to 1; set higher values to create a <tt><textarea></tt> instead. Ignored when <tt>growable: true</tt>

<tt>underline</tt>: Disable to hide the underline for the field.

<tt>clearAction</tt>: Set <tt>true</tt> to show an "X" icon for clearing.

<tt>darkBackground</tt>: Set <tt>true</tt> to invert colors: the underline will be white instead of grey and blue, the clear action will be white instead of grey.

Examples

Default

Pure defaults, <tt>onKeyMode: false</tt> and <tt>floatingLabel: true</tt>

            $$textField

Read-only version

            $$textField{ mode: 'read-only' }

<tt>onKeyMode</tt>

Editable

            $$onKeyField{ onKeyMode: true }

Read-only

            $$onKeyField{ mode: 'read-only', onKeyMode: true }

No floating label

Note the placeholder when the field is empty.

            $$textField{ floatingLabel: false }

Text areas

<tt>growable: true</tt>

            $$longString{ onKeyMode: true, growable: true }

3-line text area, with scrolling (<tt>displayHeight: 3</tt>)

            $$longString{ onKeyMode: true, displayHeight: 3, displayWidth: 20 }

    */},
    function buttonHTML() {/*

Buttons

          MD Buttons come in several flavours: raised, flat and floating action buttons.
          FOAM has built-in models for the former two.
          You can restyle a raised button into a floating action button easily.

Raised Buttons

FIX ME: We haven't needed a textual raised button.

Flat Buttons

<tt>foam.ui.md.FlatButton</tt>

          $$basicAction{ model_: 'foam.ui.md.FlatButton' }

Floating Action Buttons

So far, we've only used the one built into <tt>foam.ui.AppController</tt>.
        It's a <tt>foam.graphics.ActionButtonCView</tt> with a <tt>Flare</tt>
        and custom styling to make it floating and round.

FIX ME eventually

    */},
    function choicesHTML() {/*

Choices

There are a few flavours of choice views.

<tt>foam.ui.md.ChoiceMenuView</tt> is a pop-open menu.

<tt>foam.ui.md.ChoiceRadioView</tt> is a set of radio buttons.

        Both expect a <tt>choices</tt> property, either supplied on the property
        or passed directly to the view. This <tt>choices</tt> is an array of choices,
        in order, where the choices are either a string, or a <tt>[value, label]</tt>
        pair.

Popup menu

<tt>foam.ui.md.PopupChoiceView</tt>

          $$choiceField{ model_: 'foam.ui.md.PopupChoiceView' }

TODO Use inline styling, but without jumpy resizing when the text changes.

Radio buttons

<tt>foam.ui.md.ChoiceRadioView</tt>

          $$choiceField{ model_: 'foam.ui.md.ChoiceRadioView' }

    */},
    function booleanHTML() {/*

Booleans

There are a few flavours of boolean views as well:

<tt>foam.ui.md.ToggleView</tt> is a MD switch view, ON OFF.

<tt>foam.ui.md.CheckboxView</tt> is a standard right-aligned MD checkbox.

Toggle switches

<tt>foam.ui.md.ToggleView</tt>

          $$booleanField{ model_: 'foam.ui.md.ToggleView' }

Checkboxes

<tt>foam.ui.md.CheckboxView</tt>

          $$booleanField{ model_: 'foam.ui.md.CheckboxView' }

    */},
    function toHTML() {/*
      <div id="<%= this.id %>" class="field-guide">

          FOAM Material Design Views

General Notes

              Use <tt>mode: 'read-only'</tt> to get read-only versions of most
              views.

          <% this.textFieldHTML(out); %>
          <% this.buttonHTML(out); %>
          <% this.choicesHTML(out); %>
          <% this.booleanHTML(out); %>

    */},
  ]
});
<|endoftext|>


<commit_before>'use strict';

import $ from 'jquery';
import { Keyboard } from './foundation.util.keyboard';
import { Nest } from './foundation.util.nest';
import { Box } from './foundation.util.box';
import { rtl as Rtl } from './foundation.util.core';
import { Plugin } from './foundation.plugin';

/**
 * DropdownMenu module.
 * @module foundation.dropdown-menu
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.nest
 */

class DropdownMenu extends Plugin {
  /**
   * Creates a new instance of DropdownMenu.
   * @class
   * @name DropdownMenu
   * @fires DropdownMenu#init
   * @param {jQuery} element - jQuery object to make into a dropdown menu.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = element;
    this.options = $.extend({}, DropdownMenu.defaults, this.$element.data(), options);
    this.className = 'DropdownMenu'; // ie9 back compat

    Nest.Feather(this.$element, 'dropdown');
    this._init();

    Keyboard.register('DropdownMenu', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ARROW_RIGHT': 'next',
      'ARROW_UP': 'up',
      'ARROW_DOWN': 'down',
      'ARROW_LEFT': 'previous',
      'ESCAPE': 'close'
    });
  }

  /**
   * Initializes the plugin, and calls _prepareMenu
   * @private
   * @function
   */
  _init() {
    var subs = this.$element.find('li.is-dropdown-submenu-parent');
    this.$element.children('.is-dropdown-submenu-parent').children('.is-dropdown-submenu').addClass('first-sub');

    this.$menuItems = this.$element.find('[role="menuitem"]');
    this.$tabs = this.$element.children('[role="menuitem"]');
    this.$tabs.find('ul.is-dropdown-submenu').addClass(this.options.verticalClass);

    if (this.options.alignment === 'auto') {
        if (this.$element.hasClass(this.options.rightClass) || Rtl() || this.$element.parents('.top-bar-right').is('*')) {
            this.options.alignment = 'right';
            subs.addClass('opens-left');
        } else {
            this.options.alignment = 'left';
            subs.addClass('opens-right');
        }
    } else {
      if (this.options.alignment === 'right') {
          subs.addClass('opens-left');
      } else {
          subs.addClass('opens-right');
      }
    }
    this.changed = false;
    this._events();
  };

  _isVertical() {
    return this.$tabs.css('display') === 'block' || this.$element.css('flex-direction') === 'column';
  }

  _isRtl() {
    return this.$element.hasClass('align-right') || (Rtl() && !this.$element.hasClass('align-left'));
  }

  /**
   * Adds event listeners to elements within the menu
   * @private
   * @function
   */
  _events() {
    var _this = this,
        hasTouch = 'ontouchstart' in window || (typeof window.ontouchstart !== 'undefined'),
        parClass = 'is-dropdown-submenu-parent';

    // used for onClick and in the keyboard handlers
    var handleClickFn = function(e) {
      var $elem = $(e.target).parentsUntil('ul', `.${parClass}`),
          hasSub = $elem.hasClass(parClass),
          hasClicked = $elem.attr('data-is-click') === 'true',
          $sub = $elem.children('.is-dropdown-submenu');

      if (hasSub) {
        if (hasClicked) {
          if (!_this.options.closeOnClick || (!_this.options.clickOpen && !hasTouch) || (_this.options.forceFollow && hasTouch)) { return; }
          else {
            e.stopImmediatePropagation();
            e.preventDefault();
            _this._hide($elem);
          }
        } else {
          e.preventDefault();
          e.stopImmediatePropagation();
          _this._show($sub);
          $elem.add($elem.parentsUntil(_this.$element, `.${parClass}`)).attr('data-is-click', true);
        }
      }
    };

    if (this.options.clickOpen || hasTouch) {
      this.$menuItems.on('click.zf.dropdownmenu touchstart.zf.dropdownmenu', handleClickFn);
    }

    // Handle Leaf element Clicks
    if(_this.options.closeOnClickInside){
      this.$menuItems.on('click.zf.dropdownmenu', function(e) {
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);
        if(!hasSub){
          _this._hide();
        }
      });
    }

    if (!this.options.disableHover) {
      this.$menuItems.on('mouseenter.zf.dropdownmenu', function(e) {
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);

        if (hasSub) {
          clearTimeout($elem.data('_delay'));
          $elem.data('_delay', setTimeout(function() {
            _this._show($elem.children('.is-dropdown-submenu'));
          }, _this.options.hoverDelay));
        }
      }).on('mouseleave.zf.dropdownmenu', function(e) {
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);
        if (hasSub && _this.options.autoclose) {
          if ($elem.attr('data-is-click') === 'true' && _this.options.clickOpen) { return false; }

          clearTimeout($elem.data('_delay'));
          $elem.data('_delay', setTimeout(function() {
            _this._hide($elem);
          }, _this.options.closingTime));
        }
      });
    }
    this.$menuItems.on('keydown.zf.dropdownmenu', function(e) {
      var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'),
          isTab = _this.$tabs.index($element) > -1,
          $elements = isTab ? _this.$tabs : $element.siblings('li').add($element),
          $prevElement,
          $nextElement;

      $elements.each(function(i) {
        if ($(this).is($element)) {
          $prevElement = $elements.eq(i-1);
          $nextElement = $elements.eq(i+1);
          return;
        }
      });

      var nextSibling = function() {
        if (!$element.is(':last-child')) {
          $nextElement.children('a:first').focus();
          e.preventDefault();
        }
      }, prevSibling = function() {
        $prevElement.children('a:first').focus();
        e.preventDefault();
      }, openSub = function() {
        var $sub = $element.children('ul.is-dropdown-submenu');
        if ($sub.length) {
          _this._show($sub);
          $element.find('li > a:first').focus();
          e.preventDefault();
        } else { return; }
      }, closeSub = function() {
        //if ($element.is(':first-child')) {
        var close = $element.parent('ul').parent('li');
        close.children('a:first').focus();
        _this._hide(close);
        e.preventDefault();
        //}
      };
      var functions = {
        open: openSub,
        close: function() {
          _this._hide(_this.$element);
          _this.$menuItems.eq(0).children('a').focus(); // focus to first element
          e.preventDefault();
        },
        handled: function() {
          e.stopImmediatePropagation();
        }
      };

      if (isTab) {
        if (_this._isVertical()) { // vertical menu
          if (_this._isRtl()) { // right aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: closeSub,
              previous: openSub
            });
          } else { // left aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: openSub,
              previous: closeSub
            });
          }
        } else { // horizontal menu
          if (_this._isRtl()) { // right aligned
            $.extend(functions, {
              next: prevSibling,
              previous: nextSibling,
              down: openSub,
              up: closeSub
            });
          } else { // left aligned
            $.extend(functions, {
              next: nextSibling,
              previous: prevSibling,
              down: openSub,
              up: closeSub
            });
          }
        }
      } else { // not tabs -> one sub
        if (_this._isRtl()) { // right aligned
          $.extend(functions, {
            next: closeSub,
            previous: openSub,
            down: nextSibling,
            up: prevSibling
          });
        } else { // left aligned
          $.extend(functions, {
            next: openSub,
            previous: closeSub,
            down: nextSibling,
            up: prevSibling
          });
        }
      }
      Keyboard.handleKey(e, 'DropdownMenu', functions);

    });
  }

  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  _addBodyHandler() {
    var $body = $(document.body),
        _this = this;
    $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu')
         .on('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu', function(e) {
           var $link = _this.$element.find(e.target);
           if ($link.length) { return; }

           _this._hide();
           $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu');
         });
  }

  /**
   * Opens a dropdown pane, and checks for collisions first.
   * @param {jQuery} $sub - ul element that is a submenu to show
   * @function
   * @private
   * @fires DropdownMenu#show
   */
  _show($sub) {
    var idx = this.$tabs.index(this.$tabs.filter(function(i, el) {
      return $(el).find($sub).length > 0;
    }));
    var $sibs = $sub.parent('li.is-dropdown-submenu-parent').siblings('li.is-dropdown-submenu-parent');
    this._hide($sibs, idx);
    $sub.css('visibility', 'hidden').addClass('js-dropdown-active')
        .parent('li.is-dropdown-submenu-parent').addClass('is-active');
    var clear = Box.ImNotTouchingYou($sub, null, true);
    if (!clear) {
      var oldClass = this.options.alignment === 'left' ? '-right' : '-left',
          $parentLi = $sub.parent('.is-dropdown-submenu-parent');
      $parentLi.removeClass(`opens${oldClass}`).addClass(`opens-${this.options.alignment}`);
      clear = Box.ImNotTouchingYou($sub, null, true);
      if (!clear) {
        $parentLi.removeClass(`opens-${this.options.alignment}`).addClass('opens-inner');
      }
      this.changed = true;
    }
    $sub.css('visibility', '');
    if (this.options.closeOnClick) { this._addBodyHandler(); }
    /**
     * Fires when the new dropdown pane is visible.
     * @event DropdownMenu#show
     */
    this.$element.trigger('show.zf.dropdownmenu', [$sub]);
  }

  /**
   * Hides a single, currently open dropdown pane, if passed a parameter, otherwise, hides everything.
   * @function
   * @param {jQuery} $elem - element with a submenu to hide
   * @param {Number} idx - index of the $tabs collection to hide
   * @private
   */
  _hide($elem, idx) {
    var $toClose;
    if ($elem && $elem.length) {
      $toClose = $elem;
    } else if (idx !== undefined) {
      $toClose = this.$tabs.not(function(i, el) {
        return i === idx;
      });
    }
    else {
      $toClose = this.$element;
    }
    var somethingToClose = $toClose.hasClass('is-active') || $toClose.find('.is-active').length > 0;

    if (somethingToClose) {
      $toClose.find('li.is-active').add($toClose).attr({
        'data-is-click': false
      }).removeClass('is-active');

      $toClose.find('ul.js-dropdown-active').removeClass('js-dropdown-active');

      if (this.changed || $toClose.find('opens-inner').length) {
        var oldClass = this.options.alignment === 'left' ? 'right' : 'left';
        $toClose.find('li.is-dropdown-submenu-parent').add($toClose)
                .removeClass(`opens-inner opens-${this.options.alignment}`)
                .addClass(`opens-${oldClass}`);
        this.changed = false;
      }
      /**
       * Fires when the open menus are closed.
       * @event DropdownMenu#hide
       */
      this.$element.trigger('hide.zf.dropdownmenu', [$toClose]);
    }
  }

  /**
   * Destroys the plugin.
   * @function
   */
  _destroy() {
    this.$menuItems.off('.zf.dropdownmenu').removeAttr('data-is-click')
        .removeClass('is-right-arrow is-left-arrow is-down-arrow opens-right opens-left opens-inner');
    $(document.body).off('.zf.dropdownmenu');
    Nest.Burn(this.$element, 'dropdown');
  }
}

/**
 * Default settings for plugin
 */
DropdownMenu.defaults = {
  /**
   * Disallows hover events from opening submenus
   * @option
   * @type {boolean}
   * @default false
   */
  disableHover: false,
  /**
   * Allow a submenu to automatically close on a mouseleave event, if not clicked open.
   * @option
   * @type {boolean}
   * @default true
   */
  autoclose: true,
  /**
   * Amount of time to delay opening a submenu on hover event.
   * @option
   * @type {number}
   * @default 50
   */
  hoverDelay: 50,
  /**
   * Allow a submenu to open/remain open on parent click event. Allows cursor to move away from menu.
   * @option
   * @type {boolean}
   * @default false
   */
  clickOpen: false,
  /**
   * Amount of time to delay closing a submenu on a mouseleave event.
   * @option
   * @type {number}
   * @default 500
   */

  closingTime: 500,
  /**
   * Position of the menu relative to what direction the submenus should open. Handled by JS. Can be `'auto'`, `'left'` or `'right'`.
   * @option
   * @type {string}
   * @default 'auto'
   */
  alignment: 'auto',
  /**
   * Allow clicks on the body to close any open submenus.
   * @option
   * @type {boolean}
   * @default true
   */
  closeOnClick: true,
  /**
   * Allow clicks on leaf anchor links to close any open submenus.
   * @option
   * @type {boolean}
   * @default true
   */
  closeOnClickInside: true,
  /**
   * Class applied to vertical oriented menus, Foundation default is `vertical`. Update this if using your own class.
   * @option
   * @type {string}
   * @default 'vertical'
   */
  verticalClass: 'vertical',
  /**
   * Class applied to right-side oriented menus, Foundation default is `align-right`. Update this if using your own class.
   * @option
   * @type {string}
   * @default 'align-right'
   */
  rightClass: 'align-right',
  /**
   * Boolean to force overide the clicking of links to perform default action, on second touch event for mobile.
   * @option
   * @type {boolean}
   * @default true
   */
  forceFollow: true
};

export {DropdownMenu};
<commit_msg>Always prevent default on down arrow on the dropdown menu<commit_after>'use strict';

import $ from 'jquery';
import { Keyboard } from './foundation.util.keyboard';
import { Nest } from './foundation.util.nest';
import { Box } from './foundation.util.box';
import { rtl as Rtl } from './foundation.util.core';
import { Plugin } from './foundation.plugin';

/**
 * DropdownMenu module.
 * @module foundation.dropdown-menu
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.nest
 */

class DropdownMenu extends Plugin {
  /**
   * Creates a new instance of DropdownMenu.
   * @class
   * @name DropdownMenu
   * @fires DropdownMenu#init
   * @param {jQuery} element - jQuery object to make into a dropdown menu.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = element;
    this.options = $.extend({}, DropdownMenu.defaults, this.$element.data(), options);
    this.className = 'DropdownMenu'; // ie9 back compat

    Nest.Feather(this.$element, 'dropdown');
    this._init();

    Keyboard.register('DropdownMenu', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ARROW_RIGHT': 'next',
      'ARROW_UP': 'up',
      'ARROW_DOWN': 'down',
      'ARROW_LEFT': 'previous',
      'ESCAPE': 'close'
    });
  }

  /**
   * Initializes the plugin, and calls _prepareMenu
   * @private
   * @function
   */
  _init() {
    var subs = this.$element.find('li.is-dropdown-submenu-parent');
    this.$element.children('.is-dropdown-submenu-parent').children('.is-dropdown-submenu').addClass('first-sub');

    this.$menuItems = this.$element.find('[role="menuitem"]');
    this.$tabs = this.$element.children('[role="menuitem"]');
    this.$tabs.find('ul.is-dropdown-submenu').addClass(this.options.verticalClass);

    if (this.options.alignment === 'auto') {
        if (this.$element.hasClass(this.options.rightClass) || Rtl() || this.$element.parents('.top-bar-right').is('*')) {
            this.options.alignment = 'right';
            subs.addClass('opens-left');
        } else {
            this.options.alignment = 'left';
            subs.addClass('opens-right');
        }
    } else {
      if (this.options.alignment === 'right') {
          subs.addClass('opens-left');
      } else {
          subs.addClass('opens-right');
      }
    }
    this.changed = false;
    this._events();
  };

  _isVertical() {
    return this.$tabs.css('display') === 'block' || this.$element.css('flex-direction') === 'column';
  }

  _isRtl() {
    return this.$element.hasClass('align-right') || (Rtl() && !this.$element.hasClass('align-left'));
  }

  /**
   * Adds event listeners to elements within the menu
   * @private
   * @function
   */
  _events() {
    var _this = this,
        hasTouch = 'ontouchstart' in window || (typeof window.ontouchstart !== 'undefined'),
        parClass = 'is-dropdown-submenu-parent';

    // used for onClick and in the keyboard handlers
    var handleClickFn = function(e) {
      var $elem = $(e.target).parentsUntil('ul', `.${parClass}`),
          hasSub = $elem.hasClass(parClass),
          hasClicked = $elem.attr('data-is-click') === 'true',
          $sub = $elem.children('.is-dropdown-submenu');

      if (hasSub) {
        if (hasClicked) {
          if (!_this.options.closeOnClick || (!_this.options.clickOpen && !hasTouch) || (_this.options.forceFollow && hasTouch)) { return; }
          else {
            e.stopImmediatePropagation();
            e.preventDefault();
            _this._hide($elem);
          }
        } else {
          e.preventDefault();
          e.stopImmediatePropagation();
          _this._show($sub);
          $elem.add($elem.parentsUntil(_this.$element, `.${parClass}`)).attr('data-is-click', true);
        }
      }
    };

    if (this.options.clickOpen || hasTouch) {
      this.$menuItems.on('click.zf.dropdownmenu touchstart.zf.dropdownmenu', handleClickFn);
    }

    // Handle Leaf element Clicks
    if(_this.options.closeOnClickInside){
      this.$menuItems.on('click.zf.dropdownmenu', function(e) {
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);
        if(!hasSub){
          _this._hide();
        }
      });
    }

    if (!this.options.disableHover) {
      this.$menuItems.on('mouseenter.zf.dropdownmenu', function(e) {
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);

        if (hasSub) {
          clearTimeout($elem.data('_delay'));
          $elem.data('_delay', setTimeout(function() {
            _this._show($elem.children('.is-dropdown-submenu'));
          }, _this.options.hoverDelay));
        }
      }).on('mouseleave.zf.dropdownmenu', function(e) {
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);
        if (hasSub && _this.options.autoclose) {
          if ($elem.attr('data-is-click') === 'true' && _this.options.clickOpen) { return false; }

          clearTimeout($elem.data('_delay'));
          $elem.data('_delay', setTimeout(function() {
            _this._hide($elem);
          }, _this.options.closingTime));
        }
      });
    }
    this.$menuItems.on('keydown.zf.dropdownmenu', function(e) {
      var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'),
          isTab = _this.$tabs.index($element) > -1,
          $elements = isTab ? _this.$tabs : $element.siblings('li').add($element),
          $prevElement,
          $nextElement;

      $elements.each(function(i) {
        if ($(this).is($element)) {
          $prevElement = $elements.eq(i-1);
          $nextElement = $elements.eq(i+1);
          return;
        }
      });

      var nextSibling = function() {
        $nextElement.children('a:first').focus();
        e.preventDefault();
      }, prevSibling = function() {
        $prevElement.children('a:first').focus();
        e.preventDefault();
      }, openSub = function() {
        var $sub = $element.children('ul.is-dropdown-submenu');
        if ($sub.length) {
          _this._show($sub);
          $element.find('li > a:first').focus();
          e.preventDefault();
        } else { return; }
      }, closeSub = function() {
        //if ($element.is(':first-child')) {
        var close = $element.parent('ul').parent('li');
        close.children('a:first').focus();
        _this._hide(close);
        e.preventDefault();
        //}
      };
      var functions = {
        open: openSub,
        close: function() {
          _this._hide(_this.$element);
          _this.$menuItems.eq(0).children('a').focus(); // focus to first element
          e.preventDefault();
        },
        handled: function() {
          e.stopImmediatePropagation();
        }
      };

      if (isTab) {
        if (_this._isVertical()) { // vertical menu
          if (_this._isRtl()) { // right aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: closeSub,
              previous: openSub
            });
          } else { // left aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: openSub,
              previous: closeSub
            });
          }
        } else { // horizontal menu
          if (_this._isRtl()) { // right aligned
            $.extend(functions, {
              next: prevSibling,
              previous: nextSibling,
              down: openSub,
              up: closeSub
            });
          } else { // left aligned
            $.extend(functions, {
              next: nextSibling,
              previous: prevSibling,
              down: openSub,
              up: closeSub
            });
          }
        }
      } else { // not tabs -> one sub
        if (_this._isRtl()) { // right aligned
          $.extend(functions, {
            next: closeSub,
            previous: openSub,
            down: nextSibling,
            up: prevSibling
          });
        } else { // left aligned
          $.extend(functions, {
            next: openSub,
            previous: closeSub,
            down: nextSibling,
            up: prevSibling
          });
        }
      }
      Keyboard.handleKey(e, 'DropdownMenu', functions);

    });
  }

  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  _addBodyHandler() {
    var $body = $(document.body),
        _this = this;
    $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu')
         .on('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu', function(e) {
           var $link = _this.$element.find(e.target);
           if ($link.length) { return; }

           _this._hide();
           $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu');
         });
  }

  /**
   * Opens a dropdown pane, and checks for collisions first.
   * @param {jQuery} $sub - ul element that is a submenu to show
   * @function
   * @private
   * @fires DropdownMenu#show
   */
  _show($sub) {
    var idx = this.$tabs.index(this.$tabs.filter(function(i, el) {
      return $(el).find($sub).length > 0;
    }));
    var $sibs = $sub.parent('li.is-dropdown-submenu-parent').siblings('li.is-dropdown-submenu-parent');
    this._hide($sibs, idx);
    $sub.css('visibility', 'hidden').addClass('js-dropdown-active')
        .parent('li.is-dropdown-submenu-parent').addClass('is-active');
    var clear = Box.ImNotTouchingYou($sub, null, true);
    if (!clear) {
      var oldClass = this.options.alignment === 'left' ? '-right' : '-left',
          $parentLi = $sub.parent('.is-dropdown-submenu-parent');
      $parentLi.removeClass(`opens${oldClass}`).addClass(`opens-${this.options.alignment}`);
      clear = Box.ImNotTouchingYou($sub, null, true);
      if (!clear) {
        $parentLi.removeClass(`opens-${this.options.alignment}`).addClass('opens-inner');
      }
      this.changed = true;
    }
    $sub.css('visibility', '');
    if (this.options.closeOnClick) { this._addBodyHandler(); }
    /**
     * Fires when the new dropdown pane is visible.
     * @event DropdownMenu#show
     */
    this.$element.trigger('show.zf.dropdownmenu', [$sub]);
  }

  /**
   * Hides a single, currently open dropdown pane, if passed a parameter, otherwise, hides everything.
   * @function
   * @param {jQuery} $elem - element with a submenu to hide
   * @param {Number} idx - index of the $tabs collection to hide
   * @private
   */
  _hide($elem, idx) {
    var $toClose;
    if ($elem && $elem.length) {
      $toClose = $elem;
    } else if (idx !== undefined) {
      $toClose = this.$tabs.not(function(i, el) {
        return i === idx;
      });
    }
    else {
      $toClose = this.$element;
    }
    var somethingToClose = $toClose.hasClass('is-active') || $toClose.find('.is-active').length > 0;

    if (somethingToClose) {
      $toClose.find('li.is-active').add($toClose).attr({
        'data-is-click': false
      }).removeClass('is-active');

      $toClose.find('ul.js-dropdown-active').removeClass('js-dropdown-active');

      if (this.changed || $toClose.find('opens-inner').length) {
        var oldClass = this.options.alignment === 'left' ? 'right' : 'left';
        $toClose.find('li.is-dropdown-submenu-parent').add($toClose)
                .removeClass(`opens-inner opens-${this.options.alignment}`)
                .addClass(`opens-${oldClass}`);
        this.changed = false;
      }
      /**
       * Fires when the open menus are closed.
       * @event DropdownMenu#hide
       */
      this.$element.trigger('hide.zf.dropdownmenu', [$toClose]);
    }
  }

  /**
   * Destroys the plugin.
   * @function
   */
  _destroy() {
    this.$menuItems.off('.zf.dropdownmenu').removeAttr('data-is-click')
        .removeClass('is-right-arrow is-left-arrow is-down-arrow opens-right opens-left opens-inner');
    $(document.body).off('.zf.dropdownmenu');
    Nest.Burn(this.$element, 'dropdown');
  }
}

/**
 * Default settings for plugin
 */
DropdownMenu.defaults = {
  /**
   * Disallows hover events from opening submenus
   * @option
   * @type {boolean}
   * @default false
   */
  disableHover: false,
  /**
   * Allow a submenu to automatically close on a mouseleave event, if not clicked open.
   * @option
   * @type {boolean}
   * @default true
   */
  autoclose: true,
  /**
   * Amount of time to delay opening a submenu on hover event.
   * @option
   * @type {number}
   * @default 50
   */
  hoverDelay: 50,
  /**
   * Allow a submenu to open/remain open on parent click event. Allows cursor to move away from menu.
   * @option
   * @type {boolean}
   * @default false
   */
  clickOpen: false,
  /**
   * Amount of time to delay closing a submenu on a mouseleave event.
   * @option
   * @type {number}
   * @default 500
   */

  closingTime: 500,
  /**
   * Position of the menu relative to what direction the submenus should open. Handled by JS. Can be `'auto'`, `'left'` or `'right'`.
   * @option
   * @type {string}
   * @default 'auto'
   */
  alignment: 'auto',
  /**
   * Allow clicks on the body to close any open submenus.
   * @option
   * @type {boolean}
   * @default true
   */
  closeOnClick: true,
  /**
   * Allow clicks on leaf anchor links to close any open submenus.
   * @option
   * @type {boolean}
   * @default true
   */
  closeOnClickInside: true,
  /**
   * Class applied to vertical oriented menus, Foundation default is `vertical`. Update this if using your own class.
   * @option
   * @type {string}
   * @default 'vertical'
   */
  verticalClass: 'vertical',
  /**
   * Class applied to right-side oriented menus, Foundation default is `align-right`. Update this if using your own class.
   * @option
   * @type {string}
   * @default 'align-right'
   */
  rightClass: 'align-right',
  /**
   * Boolean to force overide the clicking of links to perform default action, on second touch event for mobile.
   * @option
   * @type {boolean}
   * @default true
   */
  forceFollow: true
};

export {DropdownMenu};
<|endoftext|>