<commit_before>/**
 * @overview ccm component for fill-in-the-blank texts
 * @author André Kless <andre.kless@web.de> 2017
 * @license The MIT License (MIT)
 */

( function () {

  var ccm_version = '8.0.0';
  var ccm_url     = '../js/ccm.js';

  var component_name = 'cloze';
  var component_obj  = {

    name: component_name,

    config: {

      text: 'Hello, [[(W)o(rl)d]]!',
      html_templates: {
        main: {
          id: 'main',
          inner: {
            tag: 'form',
            id: 'form',
            inner: [
              { id: 'keywords' },
              { id: 'text' },
              { id: 'button' },
              { id: 'timer' }
            ],
            onsubmit: '%%'
          }
        },
        keyword: {
          class: 'keyword',
          inner: '%%'
        },
        button: {
          tag: 'input',
          type: 'submit',
          value: '%caption%'
        },
        timer: {
          tag: 'span',
          inner: '%%'
        }
      },
      css_layout: [ 'ccm.load', '../css/cloze.css' ]

  //  keywords: [ 'keyword1', 'keyword2', ... ],
  //  blank: true,
  //  button_caption: 'finish',
  //  time: 60,
  //  feedback: true,
  //  logger: [ 'ccm.instance', '../log/ccm.log.js', [ 'ccm.get', '../log/configs.json', 'greedy' ] ],
  //  user:   [ 'ccm.instance', '../user/ccm.user.js' ],
  //  onchange: function ( instance, data ) { console.log( data ); },
  //  oninput:  function ( instance, data ) { console.log( data ); },
  //  onfinish: function ( instance, results ) { console.log( results ); }

    },

    Instance: function () {

      var self = this;
      var my;             // contains privatized instance members
      var keywords = [];  // information data for each keyword

      this.init = function ( callback ) {

        // fill-in-the-blank text is given via inner HTML? => use it with higher priority
        if ( self.node && self.node.innerHTML.trim() ) self.text = self.node.innerHTML;

        callback();
      };

      this.ready = function ( callback ) {

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

        var regex_keyword = /\[\[.+?\]\]/g;   // regular expression for finding all gaps/keywords in the text
        var regex_given = /\(.+?\)/g;         // regular expression for finding all given characters of a keyword

        // iterate all keywords in the text to determine the information data for each keyword
        ( my.text.match( regex_keyword ) || [] ).map( function ( keyword ) {

          // remove distinguishing characteristic '[[' and ']]'
          keyword = keyword.substr( 2, keyword.length - 4 );

          // replace all given characters of a keywords with '*'
          var keyw__d = keyword.replace( '*', '#' ).replace( regex_given, function ( given ) {
            var length = given.length - 2;
            given = '';
            for ( var i = 0; i < length; i++ ) given += '*';
            return given;
          } );

          // determine given characters and hold this information in a single number (disadvantage: possible positions
          var givens = 0;                                                      // for given letters in a word are 0-31
          for ( var i = 0; i < keyw__d.length; i++ )                           // because of data type limitations)
            if ( keyw__d.charAt( i ) === '*' ) givens += Math.pow( 2, i );

          // add keyword information data for current keyword
          keywords.push( {
            word: keyword.replace( regex_given, function ( given ) { return given.substr( 1, given.length - 2 ); } ),
            givens: givens
          } );

        } );

        // replace gaps/keywords with empty span elements
        my.text = my.text.replace( regex_keyword, '' );

        callback();
      };

      this.start = function ( callback ) {

        // initial result data
        var results = { details: [] };

        // prepare main HTML structure
        var main_elem = self.ccm.helper.html( my.html_templates.main, onFinish );

        var   text_elem  = main_elem.querySelector( '#text'   );  // container for text with containing gaps
        var button_elem  = main_elem.querySelector( '#button' );  // container for finish button
        var  timer_elem  = main_elem.querySelector( '#timer'  );  // container for timer

        // add content for inner containers
        renderKeywords();
        renderText();
        renderButton();
        renderTimer();

        // set content of own website area
        self.ccm.helper.setContent( self.element, self.ccm.helper.protect( main_elem ) );

        // has logger instance? => log start event
        if ( self.logger ) self.logger.log( 'start', my );

        if ( callback ) callback();

        /**
         * @summary renders given keywords for text gaps
         * @description
         * Keywords could be given (individual) via instance configuration (my.keywords is string array)
         * or (automatic generated) via private variable 'keywords' (my.keywords is boolean true).
         */
        function renderKeywords() {

          var keywords_elem = main_elem.querySelector( '#keywords' );  // container for keywords
          var entries = [];                                            // inner container for each keyword

          // has given keywords? => create inner container for each keyword
          if ( my.keywords )
            ( Array.isArray( my.keywords ) ? my.keywords : keywords ).map( addKeyword );
          else
            return keywords_elem.parentNode.removeChild( keywords_elem );  // no given keywords? => remove container for keywords and abort

          // generated keyword list? => sort keywords lexicographical (keyword order gives no hint about correct solution)
          if ( my.keywords === true ) entries.sort( function ( a, b ) { return a.innerHTML.localeCompare( b.innerHTML ) } );

          // add each inner keyword container to container for keywords
          entries.map( function ( entry ) { keywords_elem.appendChild( entry ); } );

          /** adds a inner container for a keyword */
          function addKeyword( keyword ) {
            entries.push( self.ccm.helper.html( my.html_templates.keyword, Array.isArray( my.keywords ) ? keyword : keyword.word ) );
          }

        }

        /** renders the text with containing gaps */
        function renderText() {

          // render text with containing gaps
          text_elem.innerHTML = my.text;

          // render input field in each gap
          self.ccm.helper.makeIterable( main_elem.querySelectorAll( '.gap' ) ).map( function ( gap_elem, i ) {

            // blank input fields and shown keywords? => input fields should give no hint for the length of the searched word
            if ( my.blank && my.keywords ) return gap_elem.appendChild( self.ccm.helper.html( { tag: 'input', type: 'text', oninput: onInput, onchange: onChange } ) );

            // shorter access to keyword
            var keyword = keywords[ i ].word;

            // prepare ccm HTML data for input field
            var input = {
              tag: 'input',
              type: 'text',
              oninput: onInput,
              onchange: onChange,
              maxlength: keyword.length,
              size: keyword.length * 1.5  // works tolerably for words with a length up to 30
            };

            // no blank input fields? => set placeholder attribute
            if ( !my.blank ) {
              input.placeholder = '';
              for ( var j = 0; j < keyword.length; j++ )
                input.placeholder += Math.pow( 2, j ) & keywords[ i ].givens ? keyword.charAt( j ) : '_';
            }

            // render input field in the current gap
            gap_elem.appendChild( self.ccm.helper.html( input ) );

            /** oninput callback for input fields */
            function onInput() {

              var data = { gap: 1 + i, input: this.value };  // input field informations

              // has logger instance? => log input event
              if ( self.logger ) self.logger.log( 'input', data );

              // has individual input callback? => perform it
              if ( self.oninput ) self.oninput( self, data );

            }

            /** onchange callback for input fields */
            function onChange() {

              var data = { gap: 1 + i, input: this.value };  // input field informations

              // has logger instance? => log change event
              if ( self.logger ) self.logger.log( 'change', data );

              // has individual change callback? => perform it
              if ( self.onchange ) self.onchange( self, data );

            }

          } );

        }

        /** renders the finish button */
        function renderButton() {

          // set content of container for finish button
          self.ccm.helper.setContent( button_elem, self.ccm.helper.html( my.html_templates.button, {
            caption: my.button_caption,
            click:   onFinish
          } ) );

          // finish button is a input tag? => allow use of browser specific default caption
          var submit_elem = button_elem.querySelector( 'input[type=submit]' );
          if ( submit_elem && !my.button_caption ) submit_elem.removeAttribute( 'value' );

        }

        /** renders the timer */
        function renderTimer() {

          var timer_value = my.time;

          // no limited time? => remove timer button and abort
          if ( !my.time ) return self.ccm.helper.removeElement( timer_elem );

          // start timer
          timer();

          /** updates countdown timer */
          function timer() {

            // already finished? => stop timer
            if ( !timer_elem ) return;

            // (re)render timer value
            self.ccm.helper.setContent( timer_elem, self.ccm.helper.html( my.html_templates.timer, timer_value ) );

            // countdown
            if ( timer_value-- )
              self.ccm.helper.wait( 1000, timer );
            else if ( button_elem )
              onFinish();           // perform finish callback at timeout

          }

        }

        /** onclick callback for finish button */
        function onFinish( event ) {

          // prevent page reload
          if ( event ) event.preventDefault();

          self.ccm.helper.removeElement( button_elem );  // remove button
          self.ccm.helper.removeElement(  timer_elem );  // remove timer container

          // has user instance? => login user
          if ( self.user ) self.user.login( proceed ); else proceed();

          function proceed() {

            // iterate over all gap input fields
            self.ccm.helper.makeIterable( main_elem.querySelectorAll( '.gap input' ) ).map( function ( gap, i ) {

              // give visual feedback for correctness
              if ( my.feedback ) {
                var nearly = gap.value.toLowerCase().trim() === keywords[ i ].word.toLowerCase().trim();
                var correct = gap.value === keywords[ i ].word;
                if ( !nearly ) gap.value = '';
                gap.setAttribute( 'placeholder', keywords[ i ].word );
                gap.parentNode.classList.add( correct ? 'correct' : ( nearly ? 'nearly' : 'wrong' ) );
              }

              // set detail informations for current gap result
              results.details.push( { input: gap.value, solution: keywords[ i ].word } );

            } );

            // finalize result data
            if ( self.user ) results.user = self.user.data().key;

            // has logger instance? => log finish event
            if ( self.logger ) self.logger.log( 'finish', results );

            // provide result data
            self.ccm.helper.onFinish( self, results );

          }

        }

      };

    }

  };

  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 ccm.cloze.js<commit_after>/**
 * @overview ccm component for rendering a fill-in-the-blank text
 * @author André Kless <andre.kless@web.de> 2017
 * @license The MIT License (MIT)
 */

( function () {

  var ccm_version = '8.0.0';
  var ccm_url     = '../js/ccm.js';

  var component_name = 'cloze';
  var component_obj  = {

    name: component_name,

    config: {

      text: 'Hello, [[(W)o(rl)d]]!',
      html_templates: {
        start: {
          id: 'start',
          inner: {
            tag: 'button',
            inner: '%caption%',
            onclick: '%click%'
          }
        },
        main: {
          id: 'main',
          inner: [
            { id: 'keywords' },
            { id: 'text' },
            {
              id: 'buttons',
              inner: [
                { id: 'cancel' },
                { id: 'submit' },
                { id: 'finish' },
                { id: 'timer' }
              ]
            }
          ]
        },
        keyword: {
          class: 'keyword',
          inner: '%%'
        },
        timer: {
          tag: 'span',
          inner: '%%'
        }
      },
      css_layout: [ 'ccm.load', '../cloze/layouts/default.css' ],
      placeholder : {
        start: 'Start',
        cancel: 'Cancel',
        finish: 'Finish'
      }

  //  start_button: true,
  //  cancel_button: true,
  //  feedback: true,
  //  keywords: [ 'keyword1', 'keyword2', ... ],
  //  blank: true,
  //  time: 60,
  //  logger: [ 'ccm.instance', '../log/ccm.log.js', [ 'ccm.get', '../log/configs.json', 'greedy' ] ],
  //  user:   [ 'ccm.instance', '../user/ccm.user.js' ],
  //  onstart: function ( instance ) { console.log( 'Fill-in-the-blank text started' ); },
  //  oncancel: function ( instance ) { console.log( 'Fill-in-the-blank text canceled' ); },
  //  onvalidation: function ( instance, data ) { console.log( data ); return true; },
  //  onfeedback: function ( instance, data ) { console.log( data ); },
  //  onchange: function ( instance, data ) { console.log( data ); },
  //  oninput:  function ( instance, data ) { console.log( data ); },
  //  onfinish: function ( instance, results ) { console.log( results ); }

    },

    Instance: function () {

      var self = this;
      var my;             // contains privatized instance members
      var keywords = [];  // information data for each keyword

      this.init = function ( callback ) {

        // fill-in-the-blank text is given via inner HTML of own Custom Element? => use it with higher priority
        if ( self.node && self.node.innerHTML.trim() ) self.text = self.node.innerHTML;

        callback();
      };

      this.ready = function ( callback ) {

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

        var regex_keyword = /\[\[.+?\]\]/g;   // regular expression for finding all gaps/keywords in the text
        var regex_given = /\(.+?\)/g;         // regular expression for finding all given characters of a keyword

        // iterate all keywords in the text to determine the information data for each keyword
        ( my.text.match( regex_keyword ) || [] ).map( function ( keyword ) {

          // remove distinguishing characteristic '[[' and ']]'
          keyword = keyword.substr( 2, keyword.length - 4 );

          // replace all given characters of a keywords with '*'
          var keyw__d = keyword.replace( '*', '#' ).replace( regex_given, function ( given ) {
            var length = given.length - 2;
            given = '';
            for ( var i = 0; i < length; i++ ) given += '*';
            return given;
          } );

          // determine given characters and hold this information in a single number (disadvantage: possible positions
          var givens = 0;                                                      // for given letters in a word are 0-31
          for ( var i = 0; i < keyw__d.length; i++ )                           // because of data type limitations)
            if ( keyw__d.charAt( i ) === '*' ) givens += Math.pow( 2, i );

          // determine and remember information data for this keyword
          keywords.push( {
            word: keyword.replace( regex_given, function ( given ) { return given.substr( 1, given.length - 2 ); } ),
            givens: givens
          } );

        } );

        // replace gaps/keywords with empty span elements
        my.text = my.text.replace( regex_keyword, '' );

        callback();
      };

      this.start = function ( callback ) {

        // has logger instance? => log 'render' event
        if ( self.logger ) self.logger.log( 'render' );

        // user must click on a start button before fill-in-the-blank text is starting? => render start button
        if ( my.start_button ) {
          self.ccm.helper.setContent( self.element, self.ccm.helper.protect( self.ccm.helper.html( my.html_templates.start, {
            caption: my.placeholder.start,
            click: start
          } ) ) );
        }
        // no need for a start button? => start fill-in-the-blank text directly
        else start();

        if ( callback ) callback();

        /** starts the fill-in-the-blank text */
        function start() {

          /**
           * initial result data
           * @type {object}
           */
          var results = { details: [] };

          // has logger instance? => log 'start' event
          if ( self.logger ) self.logger.log( 'start', my );

          // prepare main HTML structure
          var main_elem = self.ccm.helper.html( my.html_templates.main );

          // select inner containers (mostly for buttons)
          var   text_elem = main_elem.querySelector( '#text'   );
          var cancel_elem = main_elem.querySelector( '#cancel' );
          var submit_elem = main_elem.querySelector( '#submit' );
          var finish_elem = main_elem.querySelector( '#finish' );
          var  timer_elem = main_elem.querySelector( '#timer'  );

          // remove unneeded buttons
          if ( !my.cancel_button ) self.ccm.helper.removeElement( cancel_elem );
          if ( !my.feedback      ) self.ccm.helper.removeElement( submit_elem );

          // add content for inner containers
          renderKeywords();
          renderText();
          updateButtons();
          renderTimer();

          // set content of own website area
          self.ccm.helper.setContent( self.element, self.ccm.helper.protect( main_elem ) );

          // has individual 'start' callback? => perform it
          if ( self.onstart ) self.onstart( self );

          /**
           * @summary renders given keywords for text gaps
           * @description
           * Keywords could be given (individual) via instance configuration (my.keywords is string array)
           * or (automatic generated) via private variable 'keywords' (my.keywords is boolean true).
           */
          function renderKeywords() {

            /**
             * container for keywords
             * @type {Element}
             */
            var keywords_elem = main_elem.querySelector( '#keywords' );

            /**
             * contains inner container for each keyword
             * @type {Array}
             */
            var entries = [];

            // has information data for keywords? => create inner container for each keyword
            if ( my.keywords ) ( Array.isArray( my.keywords ) ? my.keywords : keywords ).map( addKeyword );
            // no given keywords? => remove container for keywords and abort
            else return self.ccm.helper.removeElement( keywords_elem );

            // generated keyword list? => sort keywords lexicographical (keyword order gives no hint about correct solution)
            if ( my.keywords === true ) entries.sort( function ( a, b ) { return a.innerHTML.localeCompare( b.innerHTML ) } );

            // add each inner keyword container to container for keywords
            entries.map( function ( entry ) { keywords_elem.appendChild( entry ); } );

            /** adds a inner container for a keyword */
            function addKeyword( keyword ) {
              entries.push( self.ccm.helper.html( my.html_templates.keyword, Array.isArray( my.keywords ) ? keyword : keyword.word ) );
            }

          }

          /** renders the fill-in-the-blank text */
          function renderText() {

            // render text with containing gaps
            text_elem.innerHTML = my.text;

            // iterate over all gap => render input field into each gap
            self.ccm.helper.makeIterable( main_elem.querySelectorAll( '.gap' ) ).map( function ( gap_elem, i ) {

              // blank input fields and shown keywords? => input fields should give no hint for the length of the searched word
              if ( my.blank && my.keywords ) return gap_elem.appendChild( self.ccm.helper.html( { tag: 'input', type: 'text', oninput: onInput, onchange: onChange } ) );

              // shorter access to keyword
              var keyword = keywords[ i ].word;

              // prepare ccm HTML data for the input field
              var input = {
                tag: 'input',
                type: 'text',
                oninput: onInput,
                onchange: onChange,
                maxlength: keyword.length,
                size: keyword.length * 1.5  // works tolerably for words with a length up to 30
              };

              // no blank input fields? => set placeholder attribute (gives informations about the characters of the searched word)
              if ( !my.blank ) {
                input.placeholder = '';
                for ( var j = 0; j < keyword.length; j++ )
                  input.placeholder += Math.pow( 2, j ) & keywords[ i ].givens ? keyword.charAt( j ) : '_';
              }

              // render input field in the current gap
              gap_elem.appendChild( self.ccm.helper.html( input ) );

              /** callback for 'input' event */
              function onInput() {

                /**
                 * event data (contains informations about the input field)
                 * @type {object}
                 */
                var event_data = { gap: 1 + i, input: this.value };

                // has logger instance? => log 'input' event
                if ( self.logger ) self.logger.log( 'input', event_data );

                // has individual 'input' callback? => perform it
                if ( self.oninput ) self.oninput( self, event_data );

              }

              /** callback for 'change' event */
              function onChange() {

                /**
                 * event data (contains informations about the input field)
                 * @type {object}
                 */
                var event_data = { gap: 1 + i, input: this.value };

                // has logger instance? => log 'change' event
                if ( self.logger ) self.logger.log( 'change', event_data );

                // has individual 'change' callback? => perform it
                if ( self.onchange ) self.onchange( self, event_data );

              }

            } );

          }

          /** (re)renders the buttons */
          function updateButtons() {

            // render 'cancel' button (if needed)
            if ( my.cancel_button ) self.ccm.helper.setContent( cancel_elem, self.ccm.helper.protect( self.ccm.helper.html( {
              tag: 'button',
              inner: my.placeholder.cancel,
              onclick: function () { if ( self.oncancel ) self.oncancel( self ); else self.start( callback ); }
            } ) ) );

            // render 'submit' button (if needed)
            if ( my.feedback ) self.ccm.helper.setContent( submit_elem, self.ccm.helper.protect( self.ccm.helper.html( {
              tag: 'button',
              disabled: results.details.length > 0,
              inner: my.placeholder.submit,
              onclick: evaluate
            } ) ) );

            // render 'finish' button
            self.ccm.helper.setContent( finish_elem, self.ccm.helper.protect( self.ccm.helper.html( {
              tag: 'button',
              inner: my.placeholder.finish,
              onclick: onFinish
            } ) ) );

            /** evaluates the fill-in-the-blank text and shows feedback */
            function evaluate() {

              // iterate over all gap input fields
              self.ccm.helper.makeIterable( main_elem.querySelectorAll( '.gap input' ) ).map( function ( gap, i ) {

                /**
                 * event data (contains informations about the input field)
                 * @type {object}
                 */
                var event_data = { gap: 1 + i, input: gap.value };

                // determine correctness of the user input value
                var nearly = gap.value.toLowerCase().trim() === keywords[ i ].word.toLowerCase().trim();
                var correct = gap.value === keywords[ i ].word;

                // has individual 'validation' callback? => perform it (reset evaluation if user input value is not valid)
                if ( self.onvalidation && !self.onvalidation( self, self.ccm.helper.clone( event_data ) ) ) return results.details = [];

                // add solution information to event data
                event_data.solution = keywords[ i ].word;

                // give visual feedback for correctness
                gap.disabled = true;
                if ( !nearly ) gap.value = '';
                gap.setAttribute( 'placeholder', keywords[ i ].word );
                gap.parentNode.classList.add( correct ? 'correct' : ( nearly ? 'nearly' : 'wrong' ) );

                // set detail informations for current gap result
                results.details.push( event_data );

              } );

              // no evaluation results? => abort
              if ( results.details.length === 0 ) return;

              // has logger instance? => log 'feedback' event
              if ( self.logger ) self.logger.log( 'feedback', results );

              // has individual 'feedback' callback? => perform it
              if ( self.onfeedback ) self.onfeedback( self, self.ccm.helper.clone( results ) );

              updateButtons();

            }

          }

          /** renders the timer */
          function renderTimer() {

            // no limited time? => remove timer button and abort
            if ( !my.time ) return self.ccm.helper.removeElement( timer_elem );

            /**
             * given seconds for working with the quiz
             * @type {number}
             */
            var timer_value = my.time;

            // start timer
            timer();

            /** updates countdown timer (recursive function) */
            function timer() {

              // no existing finish button? => stop timer
              if ( !finish_elem ) return;

              // (re)render timer value
              self.ccm.helper.setContent( timer_elem, self.ccm.helper.html( my.html_templates.timer, timer_value ) );

              // countdown
              if ( timer_value-- )
                self.ccm.helper.wait( 1000, timer );  // recursive call
              else
                onFinish();  // finish quiz at timeout

            }

          }

          /** finishes the fill-in-the-blank text */
          function onFinish() {

            // has user instance? => login user (if not already logged in)
            if ( self.user ) self.user.login( proceed ); else proceed();

            function proceed() {

              // make sure that user could not use 'finish' button again
              self.ccm.helper.removeElement( finish_elem );
              self.ccm.helper.removeElement(  timer_elem );

              // finalize result data
              if ( self.user ) results.user = self.user.data().key;

              // has logger instance? => log 'finish' event
              if ( self.logger ) self.logger.log( 'finish', results );

              // perform 'finish' actions and provide result data
              self.ccm.helper.onFinish( self, results );

            }

          }

        }

      };

    }

  };

  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>/*!
 * ui.js
 * Copyright Mathias Bynens <http://mths.be/>
 * Modified by John-David Dalton <http://allyoucanleet.com/>
 * Available under MIT license <http://mths.be/mit>
 */

(function(global, document) {

  // shortcut for typeof operators
  var FN = 'function',

   // css clasName used for error styles
   ERROR_CLASS = 'error',

   // css clasName used for `js enabled` styles
   JS_CLASS = 'js',

   // css className used to display error-info
   SHOW_CLASS = 'show',

   // css className used to reset result styles
   RESULTS_CLASS = 'results',

   // the iframe id of the Browserscope results
   BROWSERSCOPE_ID = 'browserscope',

   // seconds to wait for each stage of the Browserscope posting process (3 stages)
   BROWSERSCOPE_TIMEOUT = 2.5,

   // Google Analytics
   GA_ACCOUNT_ID = 'UA-6065217-40',

   // results element id prefix (e.g. `results-1`)
   RESULTS_PREFIX = 'results-',

   // inner text for the various run button states
   RUN_TEXT = {
     'RUNNING' : 'Stop tests',
     'RUN_AGAIN' : 'Run tests again',
     'STOPPED' : 'Run tests'
   },

   cache = {
     'counter' : 0,
     'errors' : [],
     'trash' : createElement('div')
   };

  /*--------------------------------------------------------------------------*/

  // shortcut(s)
  function $(id) {
    return document.getElementById(id);
  }

  function createElement(tag) {
    return document.createElement(tag);
  }

  // pretty print for numbers
  function formatNumber(number) {
    var comma = ',',
        string = String(Math.max(0, Math.abs(number).toFixed(0))),
        length = string.length,
        end = /^\d{4,}$/.test(string) ? length % 3 : 0;

    return (end ? string.slice(0, end) + comma : '') +
      string.slice(end).replace(/(\d{3})(?=\d)/g, '$1' + comma);
  }

  // grabs the test from the ui object that matches the id
  function getTestById(id) {
    var test,
        i = 0,
        result = null,
        tests = ui.tests;

    while (test = tests[i++]) {
      if (test.id == id) {
        result = test;
        break;
      }
    }
    return result;
  }

  // element className utility
  function hasClass(element, className) {
    return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
  }

  // a cross-browser Array#indexOf solution
  function indexOf(array, value) {
    var i = -1,
        length = this.length,
        result = i;

    if (typeof array.indexOf == FN) {
      return array.indexOf(value);
    }
    while (++i < length) {
      if (i in array && array[i] === value) {
        result = i;
        break;
      }
    }
    return result;
  }

  // like Array#join but for key-value pairs of an object
  function join(object, delimit1, delimit2) {
    var key,
        pairs = [];

    for (key in object) {
      pairs.push(key + delimit1 + object[key]);
    }
    return pairs.join(delimit2);
  }

  // appends or clears error log
  function logError(text) {
    var elTable,
        elDiv = $('error-info');

    if (!elDiv) {
      elTable = $('test-table');
      elDiv = createElement('div');
      elDiv.id = 'error-info';
      elTable.parentNode.insertBefore(elDiv, elTable.nextSibling);
    }
    if (text === false) {
      elDiv.className = elDiv.innerHTML = '';
      cache.errors = [];
    }
    else {
      text = text == null ? '' : text;
      if (indexOf(cache.errors, text) < 0) {
        cache.errors.push(text);
        elDiv.className = SHOW_CLASS;
        elDiv.innerHTML += text;
      }
    }
  }

  // sets the status text
  function logStatus(text) {
    ($('status') || { }).innerHTML = text == null ? '' : text;
  }

  /*--------------------------------------------------------------------------*/

  function onClick() {
    ui.runTest(getTestById(this.id.split('-')[1]));
  }

  function onCycle(test) {
    onStart(test);
    ui.renderTest(test);
  }

  function onHashChange() {
    ui.parseHash();
    if (typeof global.init == FN) {
      init();
    }
  }

  function onKeyUp(e) {
    // treat hitting ENTER while focused on a test title as if it were clicked
    e || (e = global.event);
    if (13 == e.keyCode) {
      onClick.call(this);
    }
  }

  function onLoad() {
    $('run').onclick = onRun;
    ($('question') || { }).value = 'no';
    $('user-agent').innerHTML = Benchmark.getPlatform();

    // auto-run tests when the URL has #run
    if ('run' == location.hash.slice(1, 3)) {
      onRun();
    }
    if (typeof global.init == FN) {
      init();
    }
  }

  function onRun(e) {
    ui[$('run').innerHTML == RUN_TEXT.RUNNING ? 'stop' : 'runAll'](e);
  }

  function onStart(test) {
    logStatus(test.name + ' × ' + formatNumber(test.count));
  }

  function onStop() {
    logStatus('Done. Ready to run tests again.');
    ui.currentTest = null;
    nextTest(ui);
  }

  /*--------------------------------------------------------------------------*/

  function addTest(name, id, fn) {
    var me = this,
        elTitle = $('title-' + id),
        test = new Benchmark(fn);

    elTitle.tabIndex = 0;
    elTitle.title = 'Click to run this test again.';
    elTitle.onclick = onClick;
    elTitle.onkeyup = onKeyUp;

    test.id = id;
    test.name = name;
    test.onCycle = onCycle;
    test.onStart = onStart;
    test.onStop = onStop;

    me.tests.push(test);
    me.elResults.push($(RESULTS_PREFIX + id));
    me.renderTest(test);
  }

  function parseHash() {
    var pair,
        hashes = location.hash.slice(1).split('&'),
        length = hashes.length,
        params = this.params = { };

    if (hashes[0]) {
      while (length--) {
        pair = hashes[length].split('=');
        params[pair[0]] = pair[1];
      }
    }
  }

  function renderTest(test) {
    var hz = test.hz,
        cell = $(RESULTS_PREFIX + test.id);

    if (test.error) {
      cell.innerHTML = 'Error';
      if (!hasClass(cell, ERROR_CLASS)) {
        cell.className += ' ' + ERROR_CLASS;
      }
      logError('
' + test.error + '.<\/p>

' + join(test.error, ': ', '<\/li>
') + '<\/li><\/ul>');
    }
    else {
      if (test.running) {
        cell.innerHTML = 'running…';
      }
      else if (indexOf(this.queue, test) > -1) {
        cell.innerHTML = 'pending…';
      }
      else if (test.count) {
        if (hz == Infinity) {
          cell.innerHTML = '∞';
          cell.title = 'Test is too fast to be recorded.';
        } else {
          cell.innerHTML = formatNumber(hz);
          cell.title = 'Looped ' + formatNumber(test.count) + ' times in ' + test.time + ' seconds.';
        }
      }
      else {
        cell.innerHTML = 'ready';
      }
    }
  }

  function runAll(e) {
    e || (e = global.event);
    var i = -1,
        me = this,
        reversed = e && e.shiftKey,
        length = me.tests.length;

    $('run').innerHTML = RUN_TEXT.RUNNING;
    while (++i < length) {
      me.runTest(me.tests[reversed ? (length - i - 1) : i]);
    }
  }

  function runTest(test) {
    var elResult,
        i = 0,
        me = this,
        elResults = me.elResults;

    if (indexOf(me.queue, test) < 0) {
      // clear error log
      logError(false);

      // reset result classNames
      while (elResult = elResults[i++]) {
        if (!hasClass(elResult, ERROR_CLASS)) {
          elResult.className = RESULTS_CLASS;
        }
      }
      // reset test before (re)running in case testing is aborted
      // so previous results are not recorded
      if (!test.error) {
        test.reset();
      }
      me.queue.push(test);
      me.renderTest(test);
      nextTest(me);
    }
  }

  function stop() {
    var me = this;
    $('run').innerHTML = RUN_TEXT.STOPPED;

    while (me.queue.length) {
      me.renderTest(me.queue.shift());
    }
  }

  function trash(element) {
    cache.trash.appendChild(element);
    cache.trash.innerHTML = '';
  }

  function nextTest(me) {
    var elResult,
        elSpan,
        first,
        last,
        length,
        percent,
        test,
        text,
        i = 0,
        result = [];

    if (me.currentTest) {
      // do nothing when running another test
    }
    else if (test = me.queue.shift()) {
      // run the next test from the queue
      me.currentTest = test;
      test.run();
    }
    else {
      // gather results
      while (test = me.tests[i++]) {
        if (test.count) {
          result.push({ 'id': test.id, 'hz': test.hz });
        }
      }
      // print results
      length = result.length;
      if (length > 1) {
        // sort descending by hz (highest hz / fastest first)
        result.sort(function(a, b) { return b.hz - a.hz; });

        i = 0;
        first = result[0];
        last = result[length - 1];

        if (first.hz != last.hz) {
          while (test = result[i++]) {
            elResult = $(RESULTS_PREFIX + test.id);
            elSpan = elResult.getElementsByTagName('span')[0];

            percent = (1 - test.hz / first.hz) * 100 || 0;
            text = test == first ? 'fastest' : Math.floor(percent) + '% slower';

            if (elSpan) {
              elSpan.innerHTML = text;
            } else {
              elResult.innerHTML += '' + text + '<\/span>';
            }
          }
          // mark fastest
          $(RESULTS_PREFIX + first.id).className += ' fastest';

          // mark slowest
          $(RESULTS_PREFIX + last.id).className += ' slowest';
        }
        // post results to Browserscope
        me.browserscope.post(me.tests);

        // all tests are finished
        $('run').innerHTML = RUN_TEXT.RUN_AGAIN;
      }
    }
  }

  /*--------------------------------------------------------------------------*/

  function post(tests) {
    var idoc,
        test,
        i = 0,
        id = BROWSERSCOPE_ID + '_' + cache.counter++,
        body = document.body,
        result = { };

    // populate result object (skipping unrun and errored tests)
    while (test = tests[i++]) {
      if (test.count) {
        // hz of Infinity will be posted but not displayed in the results
        result[(test.name.match(/[a-z0-9]+/ig) || [test.id]).join(' ')] = test.hz;
      }
    }
    // create new beacon
    try {
      elIframe = createElement('');
    } catch(e) {
      (elIframe = createElement('iframe')).name = id;
    }
    // inject beacon
    elIframe.id = id;
    elIframe.style.display = 'none';
    body.insertBefore(elIframe, body.firstChild);

    // perform inception :3
    ui._bR = result;
    idoc = global.frames[id].document;
    idoc.write('


<commit_before>M.wrap('github/jillix/bind-form/v0.1.6/ui.js', function (require, module, exports) {
var Bind = require ('github/jillix/bind');

// TODO use bind for dom interaction/manipulation
function elm(d,a) {
    try {
        var b = document.createElement(d);
        if ('object' === typeof a) {
            for (var c in a) {
                if (!a.hasOwnProperty(c)) continue;

                if (typeof a[c] === 'object' && a[c] instanceof Array) {
                    b.setAttribute(c, a[c].join(', '));
                } else {
                    b.setAttribute(c, a[c]);
                }
            }
            return b;
        }
    } catch (e) {
        return null;
    }
}
function get(s,c) {
    try {
        return (c||document).querySelector(s);
    } catch (err) {
        return null;
    }
}
function getAll(s,c){
    try{return (c||document).querySelectorAll(s);}
    catch (err) {
        return null;
    }
}

var fields = require('./ui/fields');
var controls = require('./ui/controls');
var progress = require('./ui/progress');

var formCache = {};

function getDomRefs (form) {
    var self = this;

    // return if no schema is defined
    if (!self.template.schema) {
        return;
    }

    var domRefs = {};
    var label, value, selectors;

    // get dom refs
    for (var field in self.template.schema) {
        if (!self.template.schema.hasOwnProperty(field)) continue;

        selectors = self.template.schema[field].selectors;
        if (!selectors) {
            continue;
        }

        domRefs[field] = {};

        // get the label field
        if (selectors.label && (label = getAll(selectors.label, form))) {

            for (var i = 0, l = label.length; i < l; ++i) {
                // TODO handle i18n
                var labelValue = self.template.schema[field].label;
                if (typeof labelValue === 'object') {
                    label[i].innerHTML = labelValue[M.getLocale()];
                } else {
                    label[i].innerHTML = labelValue;
                }
            }

            domRefs[field].label = label;
        }

        // get the value field
        if (value = getAll(selectors.value, form)) {
            for (var i = 0, l = value.length, tagName; i < l; ++i) {
                // set the html if the value is not an input
                if (['INPUT', 'SELECT', 'TEXTAREA'].indexOf(value[i].tagName) === -1) {
                    // set html to true to use innerHTML
                    value[i].html = true;
                    value[i].attr = selectors.attr;
                }
            }

            domRefs[field].value = value;

            // add event listeners
            for (var i = 0; i < value.length; ++i) {
                // do not add event listeners if this is not an input
                if (['INPUT', 'SELECT', 'TEXTAREA'].indexOf(value[i].tagName) === -1) {
                    continue;
                }

                // TODO
                // this is a flag used to control the state and future operation on this field
                // this will have $set or $unset values
                domRefs[field].control = '$set';

                // data change event
                var dcEvents = self.config.options.dataChanged;

                // convert string to array
                if (typeof dcEvents === "string") {
                    dcEvents = [dcEvents];
                }

                // listen to change if other event is not provided
                if (dcEvents.length === 0) {
                    dcEvents.push('change');
                }

                // listen each event in dcEvents array
                for (var j = 0; j < dcEvents.length; ++j) {
                    (function (field) {
                        value[i].addEventListener(dcEvents[j], function () {

                            var fieldValue;

                            // checkboxes need a special treatment
                            // TODO add radio button support
                            switch (this.getAttribute('type')) {
                                case 'checkbox':
                                    fieldValue = this.checked;
                                    break;
                                default:
                                    fieldValue = this.value;
                            }

                            self.emit('dataChanged', self.template._id, field, fieldValue, this);
                        }, false);
                    })(field);
                }
            }
        }
    }

    return domRefs;
}

function getTemplateHtml () {
    var self = this;

    // return if template has no html
    if (!(self.template.options || {}).html) {
        return;
    }

    // get the html
    if (!self.formCache[self.template._id]) {
        self.link(self.template.options.html, function (err, html) {
            if (err || html === '') {
                return;
            }

            // append form to the dom
            self.target.innerHTML = html;

            // cache form
            self.formCache[self.template._id] = {
                dom: self.target,
                html: html,
                refs: getDomRefs.call(self, self.target)
            };

            self.emit('formRendered', self.target);
        });
    } else {

        // append form to the dom
        self.target.innerHTML = self.formCache[self.template._id].html;
        self.formCache[self.template._id].dom = self.target;
        self.formCache[self.template._id].refs = getDomRefs.call(self, self.target);

        self.emit('formRendered', self.target);
    }
}

function autoFocus () {
    var self = this;

    if (!self.config.ui.autofocus) {
        return;
    } else {
        var target = self.config.ui.target;

        $elem = $(target + ' input[type="number"], ' + target + ' input[type="email"], ' + target + ' input[type="text"], ' + target + ' textarea, ' + target + ' select');

        //TODO find a better sollution
        setTimeout(function(){
            $elem.first().focus();
        }, 0);
    }
}

function init () {
    var self = this;

    // get form target
    if (!self.config.ui.target || !(self.target = get(self.config.ui.target, self.dom))) {
        return;
    }

    // run the binds
    for (var i = 0; i < self.config.binds.length; ++i) {
        Bind.call(self, self.config.binds[i]);
    }

    // default value for dataChanged
    self.config.options.dataChanged = self.config.options.dataChanged || [];

    // attach cache to module
    self.formCache = formCache;

    // handle template
    self.on('templateSet', getTemplateHtml);

    // init fields rendering
    self.once('formRendered', function () {

        fields.call(self);

        // init controls
        if (self.config.ui.controls) {
            controls.call(self);
        }
    });

    // init progress handling
    if (self.config.ui.progress) {
        progress.call(self);
    }

    // handle save
    self.on('dataUpdated', function (callback) {
        self.emit('saveCrud', callback);
    });
    self.on('save', function (callback) {
        self.emit('updateData', callback);
    });

    //handle autofocus
    self.on('formFilled', autoFocus);
}

module.exports = init;

return module; });<commit_msg>Added i18n form template HTML<commit_after>M.wrap('github/jillix/bind-form/v0.1.6/ui.js', function (require, module, exports) {
var Bind = require ('github/jillix/bind');

// TODO use bind for dom interaction/manipulation
function elm(d,a) {
    try {
        var b = document.createElement(d);
        if ('object' === typeof a) {
            for (var c in a) {
                if (!a.hasOwnProperty(c)) continue;

                if (typeof a[c] === 'object' && a[c] instanceof Array) {
                    b.setAttribute(c, a[c].join(', '));
                } else {
                    b.setAttribute(c, a[c]);
                }
            }
            return b;
        }
    } catch (e) {
        return null;
    }
}
function get(s,c) {
    try {
        return (c||document).querySelector(s);
    } catch (err) {
        return null;
    }
}
function getAll(s,c){
    try{return (c||document).querySelectorAll(s);}
    catch (err) {
        return null;
    }
}

var fields = require('./ui/fields');
var controls = require('./ui/controls');
var progress = require('./ui/progress');

var formCache = {};

function getDomRefs (form) {
    var self = this;

    // return if no schema is defined
    if (!self.template.schema) {
        return;
    }

    var domRefs = {};
    var label, value, selectors;

    // get dom refs
    for (var field in self.template.schema) {
        if (!self.template.schema.hasOwnProperty(field)) continue;

        selectors = self.template.schema[field].selectors;
        if (!selectors) {
            continue;
        }

        domRefs[field] = {};

        // get the label field
        if (selectors.label && (label = getAll(selectors.label, form))) {

            for (var i = 0, l = label.length; i < l; ++i) {
                // handle i18n
                var labelValue = self.template.schema[field].label;
                if (typeof labelValue === 'object') {
                    label[i].innerHTML = labelValue[M.getLocale()];
                } else {
                    label[i].innerHTML = labelValue;
                }
            }

            domRefs[field].label = label;
        }

        // get the value field
        if (value = getAll(selectors.value, form)) {
            for (var i = 0, l = value.length, tagName; i < l; ++i) {
                // set the html if the value is not an input
                if (['INPUT', 'SELECT', 'TEXTAREA'].indexOf(value[i].tagName) === -1) {
                    // set html to true to use innerHTML
                    value[i].html = true;
                    value[i].attr = selectors.attr;
                }
            }

            domRefs[field].value = value;

            // add event listeners
            for (var i = 0; i < value.length; ++i) {
                // do not add event listeners if this is not an input
                if (['INPUT', 'SELECT', 'TEXTAREA'].indexOf(value[i].tagName) === -1) {
                    continue;
                }

                // TODO
                // this is a flag used to control the state and future operation on this field
                // this will have $set or $unset values
                domRefs[field].control = '$set';

                // data change event
                var dcEvents = self.config.options.dataChanged;

                // convert string to array
                if (typeof dcEvents === "string") {
                    dcEvents = [dcEvents];
                }

                // listen to change if other event is not provided
                if (dcEvents.length === 0) {
                    dcEvents.push('change');
                }

                // listen each event in dcEvents array
                for (var j = 0; j < dcEvents.length; ++j) {
                    (function (field) {
                        value[i].addEventListener(dcEvents[j], function () {

                            var fieldValue;

                            // checkboxes need a special treatment
                            // TODO add radio button support
                            switch (this.getAttribute('type')) {
                                case 'checkbox':
                                    fieldValue = this.checked;
                                    break;
                                default:
                                    fieldValue = this.value;
                            }

                            self.emit('dataChanged', self.template._id, field, fieldValue, this);
                        }, false);
                    })(field);
                }
            }
        }
    }

    return domRefs;
}

function getTemplateHtml () {
    var self = this;

    // return if template has no html
    if (!(self.template.options || {}).html) {
        return;
    }

    // get the html
    if (!self.formCache[self.template._id]) {

        var htmlToGet = self.template.options.html;

        // handle i18n
        if (htmlToGet === 'object') {
            htmlToGet = htmlToGet[M.getLocale()];
        }

        // no HTML, no fun
        if (!htmlToGet) { return; }

        self.link(htmlToGet, function (err, html) {

            // no HTML, no fun
            if (err) { return; }

            // append form to the dom
            self.target.innerHTML = html;

            // cache form
            self.formCache[self.template._id] = {
                dom: self.target,
                html: html,
                refs: getDomRefs.call(self, self.target)
            };

            self.emit('formRendered', self.target);
        });
    } else {

        // append form to the dom
        self.target.innerHTML = self.formCache[self.template._id].html;
        self.formCache[self.template._id].dom = self.target;
        self.formCache[self.template._id].refs = getDomRefs.call(self, self.target);

        self.emit('formRendered', self.target);
    }
}

function autoFocus () {
    var self = this;

    if (!self.config.ui.autofocus) {
        return;
    } else {
        var target = self.config.ui.target;

        $elem = $(target + ' input[type="number"], ' + target + ' input[type="email"], ' + target + ' input[type="text"], ' + target + ' textarea, ' + target + ' select');

        //TODO find a better sollution
        setTimeout(function(){
            $elem.first().focus();
        }, 0);
    }
}

function init () {
    var self = this;

    // get form target
    if (!self.config.ui.target || !(self.target = get(self.config.ui.target, self.dom))) {
        return;
    }

    // run the binds
    for (var i = 0; i < self.config.binds.length; ++i) {
        Bind.call(self, self.config.binds[i]);
    }

    // default value for dataChanged
    self.config.options.dataChanged = self.config.options.dataChanged || [];

    // attach cache to module
    self.formCache = formCache;

    // handle template
    self.on('templateSet', getTemplateHtml);

    // init fields rendering
    self.once('formRendered', function () {

        fields.call(self);

        // init controls
        if (self.config.ui.controls) {
            controls.call(self);
        }
    });

    // init progress handling
    if (self.config.ui.progress) {
        progress.call(self);
    }

    // handle save
    self.on('dataUpdated', function (callback) {
        self.emit('saveCrud', callback);
    });
    self.on('save', function (callback) {
        self.emit('updateData', callback);
    });

    //handle autofocus
    self.on('formFilled', autoFocus);
}

module.exports = init;

return module; });
<|endoftext|>


<commit_before>/** global: updateScreenUrl */

/**
 * Screen class constructor
 * @param {string} updateScreenUrl global screen update checks url
 */
function Screen(updateScreenUrl) {
  this.fields = [];
  this.url = updateScreenUrl;
  this.lastChanges = null;
  this.endAt = null;
  this.nextUrl = null;
  this.cache = new Preload();
  this.debugMode = false;
}

/**
 * Ajax GET on updateScreenUrl to check lastChanges timestamp and reload if necessary
 */
Screen.prototype.checkUpdates = function() {
  var s = this;
  $.get(this.url, function(j) {
    if (j.success) {
      if (s.lastChanges == null) {
        s.lastChanges = j.data.lastChanges;
      } else if (s.lastChanges != j.data.lastChanges) {
        // Remote screen updated, we should reload
        s.reloadIn(0);
        s.nextUrl = null;
        return;
      }

      if (j.data.duration > 0) {
        // Setup next screen
        s.reloadIn(j.data.duration * 1000);
        s.nextUrl = j.data.nextScreenUrl;
      }
    } else if (j.message == 'Unauthorized') {
      screen.reloadIn(0);
    }
  });
}

/**
 * Start Screen reload procedure, checking for every field timeout
 */
Screen.prototype.reloadIn = function(minDuration) {
  var endAt = Date.now() + minDuration;
  if (this.endAt != null && this.endAt < endAt) {
    return;
  }

  if (this.cache.hasPreloadingContent(true)) {
    // Do not break preloading
    return;
  }

  this.endAt = Date.now() + minDuration;
  for (var i in this.fields) {
    if (!this.fields.hasOwnProperty(i)) {
      continue;
    }
    var f = this.fields[i];
    if (f.timeout && f.endAt > this.endAt) {
      // Always wait for content display end
      this.endAt = f.endAt;
    }
  }

  if (Date.now() >= this.endAt) {
    this.reloadNow();
  }
}

/**
 * Actual Screen reload action
 */
Screen.prototype.reloadNow = function() {
  if (this.nextUrl) {
    window.location = this.nextUrl;
  } else {
    window.location.reload();
  }
}

/**
 * Check every field for content
 * @param  {Content} data
 * @return {boolean} content is displayed
 */
Screen.prototype.displaysData = function(data) {
  return this.fields.filter(function(field) {
    return field.current && field.current.data == data;
  }).length > 0;
}

Screen.prototype.debug = function() {
  if (!this.debugMode) {
    return;
  }
  var d = $('#debug').html();
  d += Array.prototype.slice.call(arguments) + '
';
  $('#debug').html(d);
}

/**
 * Content class constructor
 * @param {array} c content attributes
 */
function Content(c) {
  this.id = c.id;
  this.data = c.data;
  this.duration = c.duration * 1000;
  this.type = c.type;
  this.src = null;

  if (this.shouldPreload()) {
    this.queuePreload();
  }
}

/**
 * Check if content should be ajax preloaded
 * @return {boolean}
 */
Content.prototype.shouldPreload = function() {
  return this.canPreload() && !this.isPreloadingOrQueued() && !this.isPreloaded();
}

/**
 * Check if content has pre-loadable material
 * @return {boolean}
 */
Content.prototype.canPreload = function() {
  return this.getResource() && this.type.search(/Video|Image|Agenda/) != -1;
}

/**
 * Check if content is displayable (preloaded and not too long)
 * @return {Boolean} can display
 */
Content.prototype.canDisplay = function() {
  return (screen.endAt == null || Date.now() + this.duration < screen.endAt) && this.isPreloaded();
}

/**
 * Extract url from contant data
 * @return {string} resource url
 */
Content.prototype.getResource = function() {
  if (this.src) {
    return this.src;
  }
  var srcMatch = this.data.match(/src="([^"]+)"/);
  if (!srcMatch) {
    return false;
  }
  var src = srcMatch[1];
  if (src.indexOf('/') === 0) {
    src = window.location.origin + src;
  }
  if (src.indexOf('http') !== 0) {
    return false;
  }
  src = src.replace(/#.*/g, '');

  this.src = src;
  return src;
}

/** Set content cache status
 * @param {string} expires header
 */
Content.prototype.setPreloadState = function(expires) {
  screen.cache.setState(this.getResource(), expires);
}

/**
 * Check cache for preload status of content
 * @return {Boolean}
 */
Content.prototype.isPreloaded = function() {
  if (!this.canPreload()) {
    return true;
  }

  return screen.cache.isPreloaded(this.getResource());
}

/**
 * Check cache for in progress or future preloading
 * @return {Boolean} is preloading
 */
Content.prototype.isPreloadingOrQueued = function() {
  return this.isPreloading() || this.isInPreloadQueue();
}

/**
 * Check cache for in progress preloading
 * @return {Boolean} is preloading
 */
Content.prototype.isPreloading = function() {
  return screen.cache.isPreloading(this.getResource());
}

/**
 * Check cache for queued preloading
 * @return {Boolean} is in preload queue
 */
Content.prototype.isInPreloadQueue = function() {
  return screen.cache.isInPreloadQueue(this.getResource());
}

/**
 * Ajax call to preload content
 */
Content.prototype.preload = function() {
  var src = this.getResource();
  if (!src) {
    return;
  }

  screen.cache.preload(src);
}

/**
 * Preload content or add to preload queue
 */
Content.prototype.queuePreload = function() {
  var src = this.getResource();
  if (!src) {
    return;
  }

  if (screen.cache.hasPreloadingContent(false)) {
    screen.debug('queue', this.src);
    this.setPreloadState(Preload.state.PRELOADING_QUEUE);
  } else {
    this.preload();
  }
}

/**
 * Preload class constructor
 * Mostly used to store constants
 */
function Preload() {
  this.cache = {};
}

Preload.prototype.setState = function(res, expires) {
  if (expires === null || expires == '') {
    expires = Preload.state.NO_EXPIRE_HEADER;
  }

  this.cache[res] = expires < -1 ? expires : Preload.state.OK
}

Preload.prototype.isPreloaded = function(res) {
  var state = this.cache[res];

  return state === Preload.state.OK || state === Preload.state.NO_EXPIRE_HEADER;
}

Preload.prototype.isPreloading = function(res) {
  return this.cache[res] === Preload.state.PRELOADING;
}

Preload.prototype.isInPreloadQueue = function(res) {
  return this.cache[res] === Preload.state.PRELOADING_QUEUE;
}

Preload.prototype.hasPreloadingContent = function(withQueue) {
  for (var res in this.cache) {
    if (!this.cache.hasOwnProperty(res)) {
      continue;
    }

    if (this.isPreloading(res) || (withQueue && this.isInPreloadQueue(res))) {
      return true;
    }
  }

  return false;
}

Preload.prototype.preload = function(res) {
  screen.debug('preloading', res)
  screen.cache.setState(res, Preload.state.PRELOADING);

  $.ajax(res).done(function(data, textStatus, jqXHR) {
    screen.cache.setState(res, jqXHR.getResponseHeader('Expires'));
    screen.debug('preloaded', res);
  }).fail(function() {
    screen.cache.setPreloadState(res, Preload.state.HTTP_FAIL);
    screen.debug('failed', res);
  }).always(function() {
    var res = screen.cache.next();
    if (res) {
      screen.debug('next', res);
      screen.cache.preload(res);
    }
  });
}

Preload.prototype.next = function() {
  for (var res in this.cache) {
    if (!this.cache.hasOwnProperty(res)) {
      continue;
    }

    if (this.isInPreloadQueue(res)) {
      return res;
    }
  }
}

/**
 * Preload states
 */
Preload.state = {
  PRELOADING: -2,
  PRELOADING_QUEUE: -3,
  HTTP_FAIL: -4,
  NO_EXPIRE_HEADER: -5,
  OK: -6,
}

/**
 * Field class constructor
 * @param {jQuery.Object} $f field object
 */
function Field($f) {
  this.$field = $f;
  this.id = $f.attr('data-id');
  this.url = $f.attr('data-url');
  this.types = $f.attr('data-types').split(' ');
  this.canUpdate = this.url != null;
  this.contents = [];
  this.previous = null;
  this.current = null;
  this.next = null;
  this.timeout = null;
  this.endAt = null;
}

/**
 * Retrieves contents from backend for this field
 */
Field.prototype.fetchContents = function() {
  if (!this.canUpdate) {
    return;
  }

  var f = this;
  $.get(this.url, function(j) {
    if (j.success) {
      f.contents = j.next.map(function(c) {
        return new Content(c);
      });
      if (!f.timeout && f.contents.length) {
        f.pickNext();
      }
    } else {
      f.setError(j.message || 'Error');
    }
  });
}

/**
 * Display error in field text
 */
Field.prototype.setError = function(err) {
  this.display(err);
}

/**
 * Randomize order
 */
Field.prototype.randomizeSortContents = function() {
  this.contents = this.contents.sort(function() {
    return Math.random() - 0.5;
  });
}

/**
 * Loop through field contents to pick next displayable content
 */
Field.prototype.pickNext = function() {
  if (screen.endAt != null && Date.now() >= screen.endAt) { // Stoping screen
    screen.reloadNow();
    return;
  }

  var f = this;
  this.previous = this.current;
  this.current = null;
  var pData = this.previous && this.previous.data;
  // Avoid repeat & other field same content
  this.randomizeSortContents();
  for (var i = 0; i < this.contents.length; i++) {
    var c = this.contents[i];
    // Skip too long or not preloaded content
    if (!c.canDisplay()) {
      continue;
    }

    if (c.data == pData) {
      // Will repeat, avoid if enough content
      if (this.contents.length < 2) {
        this.next = c;
        break;
      }
      continue;
    }

    if (screen.displaysData(c.data)) {
      // Same content already displayed on other field, avoid if enough content
      if (this.contents.length < 3) {
        this.next = c;
        break;
      }
      continue;
    }

    this.next = c;
    break
  }

  if (this.next) {
    this.displayNext();
  } else {
    setTimeout(function() {
      f.pickNext();
    }, 600);
  }
}

/**
 * Setup next content for field and display it
 */
Field.prototype.displayNext = function() {
  var f = this;
  if (this.next && this.next.duration > 0) {
    this.current = this.next
    this.next = null;
    this.display(this.current.data);
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    this.endAt = Date.now() + this.current.duration;
    this.timeout = setTimeout(function() {
      f.pickNext();
    }, this.current.duration);
  }
}

/**
 * Display data in field HTML
 * @param  {string} data
 */
Field.prototype.display = function(data) {
  if (screen.debugMode) {
    return;
  }
  this.$field.html(data);
  this.$field.show();
  if (this.$field.text() != '') {
    this.$field.textfill({
      maxFontPixels: 0,
    });
  }
}

// Global screen instance
var screen = null;

/**
 * jQuery.load event
 * Initialize Screen and Fields
 * Setup updates interval timeouts
 */
function onLoad() {
  screen = new Screen(updateScreenUrl);
  // Init
  $('.field').each(function() {
    var f = new Field($(this));
    f.fetchContents();
    screen.fields.push(f);
  });

  if (screen.url) {
    // Setup content updates loop
    setInterval(function() {
      for (var f in screen.fields) {
        if (screen.fields.hasOwnProperty(f)) {
          screen.fields[f].fetchContents();
        }
      }
      screen.checkUpdates();
    }, 30000);
    screen.checkUpdates();
  }
}

// Run
$(onLoad);
<commit_msg>Comments on new frontend.js methods<commit_after>/** global: updateScreenUrl */

/**
 * Screen class constructor
 * @param {string} updateScreenUrl global screen update checks url
 */
function Screen(updateScreenUrl) {
  this.fields = [];
  this.url = updateScreenUrl;
  this.lastChanges = null;
  this.endAt = null;
  this.nextUrl = null;
  this.cache = new Preload();
  this.debugMode = false;
}

/**
 * Ajax GET on updateScreenUrl to check lastChanges timestamp and reload if necessary
 */
Screen.prototype.checkUpdates = function() {
  var s = this;
  $.get(this.url, function(j) {
    if (j.success) {
      if (s.lastChanges == null) {
        s.lastChanges = j.data.lastChanges;
      } else if (s.lastChanges != j.data.lastChanges) {
        // Remote screen updated, we should reload
        s.reloadIn(0);
        s.nextUrl = null;
        return;
      }

      if (j.data.duration > 0) {
        // Setup next screen
        s.reloadIn(j.data.duration * 1000);
        s.nextUrl = j.data.nextScreenUrl;
      }
    } else if (j.message == 'Unauthorized') {
      screen.reloadIn(0);
    }
  });
}

/**
 * Start Screen reload procedure, checking for every field timeout
 */
Screen.prototype.reloadIn = function(minDuration) {
  var endAt = Date.now() + minDuration;
  if (this.endAt != null && this.endAt < endAt) {
    return;
  }

  if (this.cache.hasPreloadingContent(true)) {
    // Do not break preloading
    return;
  }

  this.endAt = Date.now() + minDuration;
  for (var i in this.fields) {
    if (!this.fields.hasOwnProperty(i)) {
      continue;
    }
    var f = this.fields[i];
    if (f.timeout && f.endAt > this.endAt) {
      // Always wait for content display end
      this.endAt = f.endAt;
    }
  }

  if (Date.now() >= this.endAt) {
    this.reloadNow();
  }
}

/**
 * Actual Screen reload action
 */
Screen.prototype.reloadNow = function() {
  if (this.nextUrl) {
    window.location = this.nextUrl;
  } else {
    window.location.reload();
  }
}

/**
 * Check every field for content
 * @param  {Content} data
 * @return {boolean} content is displayed
 */
Screen.prototype.displaysData = function(data) {
  return this.fields.filter(function(field) {
    return field.current && field.current.data == data;
  }).length > 0;
}

Screen.prototype.debug = function() {
  if (!this.debugMode) {
    return;
  }
  var d = $('#debug').html();
  d += Array.prototype.slice.call(arguments) + '
';
  $('#debug').html(d);
}

/**
 * Content class constructor
 * @param {array} c content attributes
 */
function Content(c) {
  this.id = c.id;
  this.data = c.data;
  this.duration = c.duration * 1000;
  this.type = c.type;
  this.src = null;

  if (this.shouldPreload()) {
    this.queuePreload();
  }
}

/**
 * Check if content should be ajax preloaded
 * @return {boolean}
 */
Content.prototype.shouldPreload = function() {
  return this.canPreload() && !this.isPreloadingOrQueued() && !this.isPreloaded();
}

/**
 * Check if content has pre-loadable material
 * @return {boolean}
 */
Content.prototype.canPreload = function() {
  return this.getResource() && this.type.search(/Video|Image|Agenda/) != -1;
}

/**
 * Check if content is displayable (preloaded and not too long)
 * @return {Boolean} can display
 */
Content.prototype.canDisplay = function() {
  return (screen.endAt == null || Date.now() + this.duration < screen.endAt) && this.isPreloaded();
}

/**
 * Extract url from contant data
 * @return {string} resource url
 */
Content.prototype.getResource = function() {
  if (this.src) {
    return this.src;
  }
  var srcMatch = this.data.match(/src="([^"]+)"/);
  if (!srcMatch) {
    return false;
  }
  var src = srcMatch[1];
  if (src.indexOf('/') === 0) {
    src = window.location.origin + src;
  }
  if (src.indexOf('http') !== 0) {
    return false;
  }
  src = src.replace(/#.*/g, '');

  this.src = src;
  return src;
}

/** Set content cache status
 * @param {string} expires header
 */
Content.prototype.setPreloadState = function(expires) {
  screen.cache.setState(this.getResource(), expires);
}

/**
 * Check cache for preload status of content
 * @return {Boolean}
 */
Content.prototype.isPreloaded = function() {
  if (!this.canPreload()) {
    return true;
  }

  return screen.cache.isPreloaded(this.getResource());
}

/**
 * Check cache for in progress or future preloading
 * @return {Boolean} is preloading
 */
Content.prototype.isPreloadingOrQueued = function() {
  return this.isPreloading() || this.isInPreloadQueue();
}

/**
 * Check cache for in progress preloading
 * @return {Boolean} is preloading
 */
Content.prototype.isPreloading = function() {
  return screen.cache.isPreloading(this.getResource());
}

/**
 * Check cache for queued preloading
 * @return {Boolean} is in preload queue
 */
Content.prototype.isInPreloadQueue = function() {
  return screen.cache.isInPreloadQueue(this.getResource());
}

/**
 * Ajax call to preload content
 */
Content.prototype.preload = function() {
  var src = this.getResource();
  if (!src) {
    return;
  }

  screen.cache.preload(src);
}

/**
 * Preload content or add to preload queue
 */
Content.prototype.queuePreload = function() {
  var src = this.getResource();
  if (!src) {
    return;
  }

  if (screen.cache.hasPreloadingContent(false)) {
    screen.debug('queue', this.src);
    this.setPreloadState(Preload.state.PRELOADING_QUEUE);
  } else {
    this.preload();
  }
}

/**
 * Preload class constructor
 * Build cache map
 */
function Preload() {
  this.cache = {};
}

/**
 * Set resource cache state
 * @param {string} res     resource url
 * @param {string|int} expires header or preload state
 */
Preload.prototype.setState = function(res, expires) {
  if (expires === null || expires == '') {
    expires = Preload.state.NO_EXPIRE_HEADER;
  }

  this.cache[res] = expires < -1 ? expires : Preload.state.OK
}

/**
 * Check resource cache for readyness state
 * @param  {string}  res resource url
 * @return {Boolean}     is preloaded
 */
Preload.prototype.isPreloaded = function(res) {
  var state = this.cache[res];

  return state === Preload.state.OK || state === Preload.state.NO_EXPIRE_HEADER;
}

/**
 * Check resource cache for preloading state
 * @param  {string}  res resource url
 * @return {Boolean}     is currently preloading
 */
Preload.prototype.isPreloading = function(res) {
  return this.cache[res] === Preload.state.PRELOADING;
}

/**
 * Check resource cache for queued preloading state
 * @param  {string}  res resource url
 * @return {Boolean}     is in preload queue
 */
Preload.prototype.isInPreloadQueue = function(res) {
  return this.cache[res] === Preload.state.PRELOADING_QUEUE;
}

/**
 * Scan resource cache for preloading resources
 * @param  {Boolean}  withQueue also check preload queue
 * @return {Boolean}           has any resource preloading/in preload queue
 */
Preload.prototype.hasPreloadingContent = function(withQueue) {
  for (var res in this.cache) {
    if (!this.cache.hasOwnProperty(res)) {
      continue;
    }

    if (this.isPreloading(res) || (withQueue && this.isInPreloadQueue(res))) {
      return true;
    }
  }

  return false;
}

/**
 * Preload a resource by ajax get on the url
 * Check HTTP return state to validate proper cache
 * @param  {string} res resource url
 */
Preload.prototype.preload = function(res) {
  screen.debug('preloading', res)
  screen.cache.setState(res, Preload.state.PRELOADING);

  $.ajax(res).done(function(data, textStatus, jqXHR) {
    screen.cache.setState(res, jqXHR.getResponseHeader('Expires'));
    screen.debug('preloaded', res);
  }).fail(function() {
    screen.cache.setPreloadState(res, Preload.state.HTTP_FAIL);
    screen.debug('failed', res);
  }).always(function() {
    var res = screen.cache.next();
    if (res) {
      screen.debug('next', res);
      screen.cache.preload(res);
    }
  });
}

/**
 * Get next resource to preload from queue
 * @return {string|null} next resource url
 */
Preload.prototype.next = function() {
  for (var res in this.cache) {
    if (!this.cache.hasOwnProperty(res)) {
      continue;
    }

    if (this.isInPreloadQueue(res)) {
      return res;
    }
  }
  return null;
}

/**
 * Preload states
 */
Preload.state = {
  PRELOADING: -2,
  PRELOADING_QUEUE: -3,
  HTTP_FAIL: -4,
  NO_EXPIRE_HEADER: -5,
  OK: -6,
}

/**
 * Field class constructor
 * @param {jQuery.Object} $f field object
 */
function Field($f) {
  this.$field = $f;
  this.id = $f.attr('data-id');
  this.url = $f.attr('data-url');
  this.types = $f.attr('data-types').split(' ');
  this.canUpdate = this.url != null;
  this.contents = [];
  this.previous = null;
  this.current = null;
  this.next = null;
  this.timeout = null;
  this.endAt = null;
}

/**
 * Retrieves contents from backend for this field
 */
Field.prototype.fetchContents = function() {
  if (!this.canUpdate) {
    return;
  }

  var f = this;
  $.get(this.url, function(j) {
    if (j.success) {
      f.contents = j.next.map(function(c) {
        return new Content(c);
      });
      if (!f.timeout && f.contents.length) {
        f.pickNext();
      }
    } else {
      f.setError(j.message || 'Error');
    }
  });
}

/**
 * Display error in field text
 */
Field.prototype.setError = function(err) {
  this.display(err);
}

/**
 * Randomize order
 */
Field.prototype.randomizeSortContents = function() {
  this.contents = this.contents.sort(function() {
    return Math.random() - 0.5;
  });
}

/**
 * Loop through field contents to pick next displayable content
 */
Field.prototype.pickNext = function() {
  if (screen.endAt != null && Date.now() >= screen.endAt) { // Stoping screen
    screen.reloadNow();
    return;
  }

  var f = this;
  this.previous = this.current;
  this.current = null;
  var pData = this.previous && this.previous.data;
  // Avoid repeat & other field same content
  this.randomizeSortContents();
  for (var i = 0; i < this.contents.length; i++) {
    var c = this.contents[i];
    // Skip too long or not preloaded content
    if (!c.canDisplay()) {
      continue;
    }

    if (c.data == pData) {
      // Will repeat, avoid if enough content
      if (this.contents.length < 2) {
        this.next = c;
        break;
      }
      continue;
    }

    if (screen.displaysData(c.data)) {
      // Same content already displayed on other field, avoid if enough content
      if (this.contents.length < 3) {
        this.next = c;
        break;
      }
      continue;
    }

    this.next = c;
    break
  }

  if (this.next) {
    this.displayNext();
  } else {
    setTimeout(function() {
      f.pickNext();
    }, 600);
  }
}

/**
 * Setup next content for field and display it
 */
Field.prototype.displayNext = function() {
  var f = this;
  if (this.next && this.next.duration > 0) {
    this.current = this.next
    this.next = null;
    this.display(this.current.data);
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    this.endAt = Date.now() + this.current.duration;
    this.timeout = setTimeout(function() {
      f.pickNext();
    }, this.current.duration);
  }
}

/**
 * Display data in field HTML
 * @param  {string} data
 */
Field.prototype.display = function(data) {
  if (screen.debugMode) {
    return;
  }
  this.$field.html(data);
  this.$field.show();
  if (this.$field.text() != '') {
    this.$field.textfill({
      maxFontPixels: 0,
    });
  }
}

// Global screen instance
var screen = null;

/**
 * jQuery.load event
 * Initialize Screen and Fields
 * Setup updates interval timeouts
 */
function onLoad() {
  screen = new Screen(updateScreenUrl);
  // Init
  $('.field').each(function() {
    var f = new Field($(this));
    f.fetchContents();
    screen.fields.push(f);
  });

  if (screen.url) {
    // Setup content updates loop
    setInterval(function() {
      for (var f in screen.fields) {
        if (screen.fields.hasOwnProperty(f)) {
          screen.fields[f].fetchContents();
        }
      }
      screen.checkUpdates();
    }, 30000);
    screen.checkUpdates();
  }
}

// Run
$(onLoad);
<|endoftext|>


<commit_before>
var fayeClient = new Faye.Client('http://es.fungalgenomics.ca:9999/montr');
var treeInterface = { hover: function(anno) {}, select: function(anno) {} };
(function() {
// General setup and functions
var  sbUser = window.senseBase.user;
var currentURI;
window.myID = sbUser + new Date().getTime();
console.log('myID', window.myID);

// receive annotations
fayeClient.subscribe('/annotations', function(data) {
  var annotations = data.annotations, uri = data.uri;
  console.log('/annotations', data);
  // it's not current but needs to be updated
  if (uri !== currentURI) {
    //FIXME should only update if its in current results
    console.log('not current uri');
    return;
  }
// it's our current item, display
// group by annotator
  console.log('updating current');
  displayAnnoTree(annotations, uri, treeInterface);
});

}());

var encIDs = [];
// encode a string (URI) for an ID
function encID(c) {
  return 'enc' + (encIDs.indexOf(c) > -1 ? encIDs.indexOf(c) : encIDs.push(c) - 1);
}

function deEncID(c) {
  return encIDs[c.replace('enc', '')];
}

// sets the current annotation loc
function setCurrentURI(u) {
  currentURI = u.replace(/#.*/, '').replace(/#$/, '');
//  addChat('is visiting ' + '' + currentURI + '');
}

// display all annotations, then return a structure containing instances mapped to IDs
function displayAnnoTree(annotations, uri) {
  // utility to manage IDs for items
  var treeItems = {
    // ids and their items
    map : {},
    // id issued in sequence
    _id : 0,
    reset : function() {
      this.map = {};
      this._id = 0;
    },
    // get an id
    id : function(o) {
      this._id++;
      o.__id = this._id;
      this.map[this._id] = o;
      return this._id;
    },
    // return the item for this id
    get : function(i) {
      return this.map[i];
    }
  };

  // create positions of tree items
  var treeMap = {}, treeRoot = { text: 'Annotations', children: []}, annoTotal = 0, curParent;
  annotations.forEach(function(cur) {
    annoTotal++;
    // get parent position (current position without current)
    if (!cur.position) {
      console.log('missing position', cur);
      return;
    }
    var ppos = cur.position.slice(0, cur.position.length - 1);
    // find or create parents
    var roots = [], curAdd = treeRoot;

    ppos.forEach(function(cpos) {
      roots.push(cpos);
      curParent = treeMap[roots];
      if (!curParent) {
        curParent = { text: cpos, children: [] };
        console.log('creating', curParent, roots);
        treeMap[roots] = curParent;
        curAdd.children.push(curParent);
      }
        curAdd = curParent;
    });

    if (treeMap[cur.position]) {
      cur.children = treeMap[cur.position].children;
      cur.id = treeMap[cur.position].id || id(cur);
    } else {
      cur.children = [];
      cur.id = treeItems.id(cur);
    }
    cur.text = cur.position[cur.position.length - 1];

    // add ranges
    if (cur.type === 'quote') {
      var instances = [];
      cur.ranges.forEach(function(r) {
        instances.push({ type: 'range', text: r.exact, id: treeItems.id(r) });
      });
      cur.children = instances;
    // display key : value
    } else if (cur.type === 'value' || cur.type === 'valueQuote') {
      cur.text = cur.key + ':' + cur.value;
    // should be ok as-is
    } else if (cur.type === 'category') {
    }

    curParent.children.push(cur);
    treeMap[cur.position] = cur;
  });

  console.log('TREE', treeRoot, annoTotal);

  $('#annotationCount').html(annoTotal);

  $.jstree.defaults.core.themes.responsive = false;

  $('#treeContainer').html('

');
  $('#annoTree').on('hover_node.jstree', function(e, data) {
    var anno = treeItems.get(data.node.id);
    treeInterface.hover(anno);
  }).on('select_node.jstree', function(e, data) {
    var anno = treeItems.get(data.node.id);
    treeInterface.select(anno);
  }).jstree({
    core : { data: treeRoot },
    plugins : [ "search", "types", "wholerow" ],
    types : {
      default : {
        icon : "tags icon"
      },
      range : {
        icon : "ellipsis horizontal icon"
      },
      category : {
        icon : "tag icon"
      },
      valueQuote : {
        icon : "text width icon"
      },
      value : {
        icon : "info letter icon"
      },
      quote : {
        icon : "quote left icon"
      }
   }
  });

  console.log('treeAnnos', treeMap);
  return treeItems;
}

<commit_msg>cleanup<commit_after><|endoftext|>


<commit_before>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platfroms allow them to do with
// text.
//
// Copyright (C) 2006-2019  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('14.15.0');
		this.setClientTag('node:19-02-28');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<commit_msg>New clientlib ver<commit_after>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platfroms allow them to do with
// text.
//
// Copyright (C) 2006-2019  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('14.15.0');
		this.setClientTag('node:19-03-01');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<|endoftext|>


<commit_before>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platforms allow them to do with
// text.
//
// Copyright (C) 2006-2022  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('18.7.0');
		this.setClientTag('node:22-06-18');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<commit_msg>New clientlib ver<commit_after>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platforms allow them to do with
// text.
//
// Copyright (C) 2006-2022  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('18.7.0');
		this.setClientTag('node:22-06-19');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<|endoftext|>


<commit_before>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platforms allow them to do with
// text.
//
// Copyright (C) 2006-2021  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('17.5.0');
		this.setClientTag('node:21-08-24');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<commit_msg>New clientlib ver<commit_after>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platforms allow them to do with
// text.
//
// Copyright (C) 2006-2021  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('17.5.0');
		this.setClientTag('node:21-08-25');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<|endoftext|>


<commit_before>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platfroms allow them to do with
// text.
//
// Copyright (C) 2006-2020  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('16.7.0');
		this.setClientTag('node:20-07-30');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<commit_msg>New clientlib ver<commit_after>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platfroms allow them to do with
// text.
//
// Copyright (C) 2006-2020  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('16.7.0');
		this.setClientTag('node:20-07-31');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<|endoftext|>