<commit_before>Zepto(function($) {
  //Cached elements
  var $window = $(window);
  var $mainNav = $('#main-nav');
  var $navLogo = $mainNav.children('.logo');

  var $sections = $('.section');
  var $sectionIntro = $sections.filter('[data-section="home"]');
  var $sectionWork = $sections.filter('[data-section="work"]');
  var $sectionSkills = $sections.filter('[data-section="skills"]');
  var $sectionContact = $sections.filter('[data-section="contact"]');

  var $introParticles = $('#intro-particles');
  var $introMiddle = $sectionIntro.children('.middle');
  var $introLogo = $introMiddle.children('.logo');

  var $skillsParticles = $('#skills-particles');

  var $contactForm = $sectionContact.find('form');
  var $contactEmail = $contactForm.find('#contact-email');
  var $contactMessage = $contactForm.find('#contact-message');

  //Constants that aren't elements...
  var contactMessageMin = parseInt($contactMessage.attr('minlength'));
  var contactMessageMax = parseInt($contactMessage.attr('maxlength'));

  //State tracking
  var initialLoad = true;
  var prevY = null;
  var prevW = null;
  var scrollPoints = {};
  var scrollInElements = [];
  var previousSection = '';
  var currentSection = '';
  var preventScrollSectionChange = true;
  var preventScrollSectionAnim = false;
  var contactSending = false;

  /**********************************
   * Methods
   **********************************/

  function getScrollPos() {
    return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  }

  //adapted from https://stackoverflow.com/questions/21474678/scrolltop-animation-without-jquery
  function scrollTo(to, duration, cb) {
    var cosParameter = (getScrollPos() - to) / 2,
      scrollCount = 0,
      oldTimestamp  = window.performance.now();

    function step(newTimestamp) {
      scrollCount += Math.PI / (duration / (newTimestamp - oldTimestamp));

      if (scrollCount >= Math.PI) {
        window.scrollTo(0, to); //ensures we actually end up at the right place, not slightly off it

        if ($.isFunction(cb)) {
          cb();
        }

        return;
      }

      window.scrollTo(0, Math.round(to + cosParameter + cosParameter * Math.cos(scrollCount)));
      oldTimestamp = newTimestamp;
      window.requestAnimationFrame(step);
    }
    window.requestAnimationFrame(step);
  }

  function gotoPage(page) {
    history.replaceState({page: page}, '', page === 'home' ? '/' : page);
    $window.trigger('popstate');
  }

  function getSectionElement(sectionName) {
    return $sections.filter('[data-section="'+sectionName+'"]');
  }

  function activateNavItem() {
    var $item = $mainNav.find('.page-links a[href="/'+ currentSection +'"]');
    $mainNav.find('.page-links a').not($item).removeClass('active');
    $item.addClass('active');
  }

  function inViewport($el) {
    var elH = $el.height(),
      H   = window.innerHeight,
      r   = $el[0].getBoundingClientRect(), t=r.top, b=r.bottom;
    return Math.max(0, t>0? Math.min(elH, H-t) : (b<H?b:H));
  }

  function registerScrollInEvent($el, handler, offset) {
    scrollInElements.push({
      pos: 99999999,
      offset: offset ? offset : 0,
      $el: $el,
      handler: handler
    });
  }

  function afterPopScroll() {
    preventScrollSectionChange = false;
  }

  function ContactValidationException(message) {
    this.message = message;
  }

  function validateContactEmail() {
    var email = $contactEmail.val();

    if (email.length < 3) {
      throw new ContactValidationException();
    }
  }

  function validateContactMessage() {
    var message = $contactMessage.val();

    if (message.length < contactMessageMin) {
      throw new ContactValidationException('Your message is a tad too short! Please enter at least ' +
                                            contactMessageMin + ' characters.');
    }

    if (message.length > contactMessageMax) {
      throw new ContactValidationException('Your message is a bit too long! Please keep it under ' +
                                            contactMessageMin + ' characters.');
    }
  }

  function toggleErrorTip(mode, $el, text) {
    var $parent = $el.parent();

    if (mode === 'show') {
      $parent.removeClass('valid').addClass('error');
      $parent.find('.error-tip').text(text);
    } else if (mode === 'hide') {
      $parent.removeClass('error');
    }
  }

  /**********************************
   * Events
   **********************************/

  $window.on('scroll', function(e, force) {
    var currentY = getScrollPos();
    //Only update on vertical scroll change
    if (prevY !== currentY || force) {
      var viewHeight = document.documentElement.clientHeight;

      if (!preventScrollSectionChange && !initialLoad) {
        //Change url if a new section has the majority of screen height
        $sections.each(function (i, el) {
          var $el = $(el);
          var section = $el.data('section');
          if (inViewport($el) > viewHeight / 2 && currentSection !== section) {
            preventScrollSectionAnim = true;
            gotoPage(section);
          }
        });
      }

      //Toggle a dark bg on nav
      if (currentY >= scrollPoints.navBg && !$mainNav.hasClass('has-bg')) {
        $mainNav.addClass('has-bg');
      } else if (currentY < scrollPoints.navBg && $mainNav.hasClass('has-bg')) {
        $mainNav.removeClass('has-bg');
      }

      //Show logo in nav if we scrolled passed the one in intro
      if (currentY >= scrollPoints.navLogo && !$mainNav.hasClass('has-logo')) {
        $navLogo.attr('aria-hidden', false);
        $mainNav.addClass('has-logo');
      } else if (currentY < scrollPoints.navLogo && $mainNav.hasClass('has-logo')) {
        $navLogo.attr('aria-hidden', true);
        $mainNav.removeClass('has-logo');
      }

      //Don't need to animate intro particles if we can't see them!
      if (introParticles) {
        var visible = currentY < scrollPoints.introParticles;
        if (!introParticles.active && visible) {
          particlesJS.resume(introParticles);
        } else if (introParticles.active && !visible) {
          particlesJS.pause(introParticles);
        }
      }

      //Same with the skills particles
      if (skillsParticles) {
        var visible = currentY + viewHeight > scrollPoints.skillsParticlesTop &&
                      currentY < scrollPoints.skillsParticlesBottom;
        if (!skillsParticles.active && visible) {
          particlesJS.resume(skillsParticles);
        } else if (skillsParticles.active && !visible) {
          particlesJS.pause(skillsParticles);
        }
      }

      //Handle elements that have scrolled into view
      var s = scrollInElements.length;
      while (s--) {
        var scrollIn = scrollInElements[s];
        if ((currentY + viewHeight) > scrollIn.pos) {
          scrollIn.handler(scrollIn.$el);
          scrollInElements.splice(scrollInElements.indexOf(scrollIn), 1);
        }
      }

      prevY = currentY;
    }
  });

  function resizeHandler(force) {
    var currentW = document.documentElement.clientWidth;
    //Only update on width change
    if (prevW !== currentW || force) {
      console.log('Resize fired', $skillsParticles.offset().top);

      //Update scroll detection points
      scrollPoints.navBg = $introMiddle.offset().top;
      scrollPoints.navLogo = $introLogo.offset().top + $introLogo.height();
      scrollPoints.introParticles = $introParticles.offset().top + $introParticles.height();
      scrollPoints.skillsParticlesTop = $skillsParticles.offset().top;
      scrollPoints.skillsParticlesBottom = scrollPoints.skillsParticlesTop + $skillsParticles.height();

      $.each(scrollInElements, function(key, scrollIn) {
        scrollIn.pos = scrollIn.$el.offset().top + scrollIn.offset;
      });

      prevW = currentW;
    }
  }

  $window.on('resize', function() {
    resizeHandler();
  });

  $('a[href^="/"]').not('[href*="."]').not('[target="_blank"]').click(function() {
    gotoPage($(this).attr('href').substring(1));
    return false;
  });

  $window.on('popstate', function() {
    var page = history.state && history.state.page ? history.state.page : location.pathname.substring(1);
    previousSection = currentSection;
    currentSection = page !== '' ? page : 'home';

    var $target = getSectionElement(currentSection);
    activateNavItem();

    if (!preventScrollSectionAnim) {
      if ($target.length > 0) {
        preventScrollSectionChange = true;

        if (initialLoad) {
          if ($target.offset().top) {
              window.scrollTo(0, $target.offset().top);
          }
          afterPopScroll();

        } else {
          var scrollAnimComplete = false;
          scrollTo($target.offset().top, 250, function() {
            if (!scrollAnimComplete) {
              scrollAnimComplete = true;
              afterPopScroll();
            }
          });
        }
      }
    }

    preventScrollSectionAnim = false;
  });

  //Contact form stuff

  $contactEmail.on('blur keypress', function(e) {
    var $parent = $contactEmail.parent();
    if (e.type === 'keypress' && !$parent.hasClass('error')) {
      return true;
    }

    try {
      validateContactEmail();
      toggleErrorTip('hide', $contactEmail);
      $parent.addClass('valid');
    } catch (e) {
      if (e instanceof ContactValidationException) {
        toggleErrorTip('show', $contactEmail, 'Please enter a valid email address.');
      }
    }
  });

  $contactMessage.on('blur keypress', function(e) {
    var $parent = $contactMessage.parent();
    if (e.type === 'keypress' && !$parent.hasClass('error')) {
      return true;
    }

    try {
      validateContactMessage();
      toggleErrorTip('hide', $contactMessage);
      $parent.addClass('valid');
    } catch (e) {
      if (e instanceof ContactValidationException) {
        toggleErrorTip('show', $contactMessage, e.message);
      }
    }
  });

  $contactForm.find('.alert').on('click', '.close', function() {
    $(this).closest('.alert').removeClass('active');
    $contactForm.removeClass('has-alert');
  });

  $sectionContact.find('form').on('submit', function(e) {
    e.preventDefault();
    var errors = false;

    try {
      validateContactEmail();
      toggleErrorTip('hide', $contactEmail);
    } catch (e) {
      if (e instanceof ContactValidationException) {
        errors = true;
        toggleErrorTip('show', $contactEmail, 'Please enter a valid email address.');
      }
    }

    try {
      validateContactMessage();
      toggleErrorTip('hide', $contactMessage);
    } catch (e) {
      if (e instanceof ContactValidationException) {
        errors = true;
        toggleErrorTip('show', $contactMessage, e.message);
      }
    }

    if (errors || contactSending) {
      return false;
    }

    //submit form via ajax
    contactSending = true;
    $contactForm.find('button[type="submit"]').attr('disabled', 'disabled').addClass('loading');

    $.ajax({
      type: 'POST',
      url: $contactForm.attr('action'),
      dataType: 'json',
      data: {
        ajax: true,
        name: $contactForm.find('#contact-name').val(),
        email: $contactEmail.val(),
        message: $contactMessage.val(),
        url: $contactForm.find('#contact-url').val()
      },
      complete: function() {
        //Ensure the form always unlocks regardless of error state
        $contactForm.find('button[type="submit"]').removeAttr('disabled').removeClass('loading');
        $contactForm.children('fieldset').removeClass('valid error');
        contactSending = false;
      },
      success: function(res) {
        if (res.error) {
          //Something broke on the server welp
          $contactForm.addClass('has-alert');
          $contactForm.find('.send-error').addClass('active')
            .find('.error-code').text(res.code ? '(Err: ' + res.code + ')' : '');

        } else if (res.errors) {
          //User got validation errors (bypassed the html/js rules?)
          $.each(res.errors, function (type, message) {
            if (type === 'email') {
              toggleErrorTip('show', $contactEmail, message);
            } else if (type === 'message') {
              toggleErrorTip('show', $contactMessage, message);
            }
          });

        } else if (res.success) {
          //Message actually went through!
          $contactForm.get(0).reset();
          $contactForm.addClass('has-alert');
          $contactForm.find('.send-success').addClass('active');
        }
      },
      error: function() {
        //Something broke on the server welp
        $contactForm.addClass('has-alert');
        $contactForm.find('.send-error').addClass('active')
          .find('.error-code').text('(Err: 99)');
      }
    });
  });

  /**********************************
   * Scroll-in visibility effects
   **********************************/

  $sections.not($sectionIntro).find('header .title').each(function(i, el) {
    registerScrollInEvent($(el), function($el) {
      $el.addClass('active');
    }, 100);
  });

  $sectionWork.find('.showcase .item .logo').each(function(i, el) {
    registerScrollInEvent($(el), function ($el) {
      $el.addClass('active');
    }, 100);
  });

  $sectionWork.find('.showcase .item .info').each(function(i, el) {
    registerScrollInEvent($(el), function ($el) {
      $el.addClass('active');
    }, 100);
  });

  $sectionSkills.find('article > p, .resume').each(function(i, el) {
    registerScrollInEvent($(el), function ($el) {
      $el.addClass('active');
    });
  });

  $sectionSkills.find('article > ul li').each(function(i, el) {
    registerScrollInEvent($(el), function ($el) {
      $el.addClass('active');
    });
  });

  $sectionContact.find('article > p, form button[type="submit"]').each(function(i, el) {
    registerScrollInEvent($(el), function ($el) {
      $el.addClass('active');
    });
  });

  $sectionContact.find('form > fieldset').each(function(i, el) {
    registerScrollInEvent($(el), function ($el) {
      $el.addClass('active');
    }, ($(el).height() / 4) * -1);
  });

  $sectionContact.find('.social a').each(function(i, el) {
    registerScrollInEvent($(el), function ($el) {
      $el.addClass('active');
    });
  });

  /**********************************
   * Init
   **********************************/

  var introParticles = particlesJS('intro-particles', window.introParticlesConf);
  var skillsParticles = particlesJS('skills-particles', window.skillsParticlesConf);

  //ghetto do-it-yourself ladda.js
  $('.dh-btn.loading-enabled').each(function(i, el) {
    var $el = $(el);
    var html = $el.html();
    $el.empty();
    $el.append('' + html + '')
      .append('');
  });

  $sectionIntro.addClass('active');

  resizeHandler();
  $window.trigger('popstate');
  $window.trigger('scroll');

  initialLoad = false;

  window.dhDebug = function() {
    console.log(scrollPoints);
  };

  setTimeout(function() {
    resizeHandler(true);
  }, 500);
});<commit_msg>Rewrote scroll-in-elements stuff. Should solve fringe issues on a certain browser.<commit_after>Zepto(function($) {
  //Cached elements
  var $body = $('body');
  var $window = $(window);
  var $mainNav = $('#main-nav');
  var $navLogo = $mainNav.children('.logo');

  var $sections = $('.section');
  var $sectionIntro = $sections.filter('[data-section="home"]');
  var $sectionWork = $sections.filter('[data-section="work"]');
  var $sectionSkills = $sections.filter('[data-section="skills"]');
  var $sectionContact = $sections.filter('[data-section="contact"]');

  var $introParticles = $('#intro-particles');
  var $introMiddle = $sectionIntro.children('.middle');
  var $introLogo = $introMiddle.children('.logo');

  var $skillsParticles = $('#skills-particles');

  var $contactForm = $sectionContact.find('form');
  var $contactEmail = $contactForm.find('#contact-email');
  var $contactMessage = $contactForm.find('#contact-message');

  //Constants that aren't elements...
  var contactMessageMin = parseInt($contactMessage.attr('minlength'));
  var contactMessageMax = parseInt($contactMessage.attr('maxlength'));

  //State tracking
  var initialLoad = true;
  var prevY = null;
  var scrollInElements = [];
  var previousSection = '';
  var currentSection = '';
  var preventScrollSectionChange = true;
  var preventScrollSectionAnim = false;
  var contactSending = false;

  /**********************************
   * Methods
   **********************************/

  function getScrollPos() {
    return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  }

  //adapted from https://stackoverflow.com/questions/21474678/scrolltop-animation-without-jquery
  function scrollTo(to, duration, cb) {
    var cosParameter = (getScrollPos() - to) / 2,
      scrollCount = 0,
      oldTimestamp  = window.performance.now();

    function step(newTimestamp) {
      scrollCount += Math.PI / (duration / (newTimestamp - oldTimestamp));

      if (scrollCount >= Math.PI) {
        window.scrollTo(0, to); //ensures we actually end up at the right place, not slightly off it

        if ($.isFunction(cb)) {
          cb();
        }

        return;
      }

      window.scrollTo(0, Math.round(to + cosParameter + cosParameter * Math.cos(scrollCount)));
      oldTimestamp = newTimestamp;
      window.requestAnimationFrame(step);
    }
    window.requestAnimationFrame(step);
  }

  function gotoPage(page) {
    history.replaceState({page: page}, '', page === 'home' ? '/' : page);
    $window.trigger('popstate');
  }

  function getSectionElement(sectionName) {
    return $sections.filter('[data-section="'+sectionName+'"]');
  }

  function activateNavItem() {
    var $item = $mainNav.find('.page-links a[href="/'+ currentSection +'"]');
    $mainNav.find('.page-links a').not($item).removeClass('active');
    $item.addClass('active');
  }

  function inViewport($el) {
    var elH = $el.height(),
      H   = window.innerHeight,
      r   = $el[0].getBoundingClientRect(), t=r.top, b=r.bottom;
    return Math.max(0, t>0? Math.min(elH, H-t) : (b<H?b:H));
  }

  function isVisible(currentY, viewHeight, $el, offset) {
    return (currentY + viewHeight) > $el.offset().top + offset;
  }

  function registerScrollInEvent($el, offset) {
    scrollInElements.push({
      $el: $el,
      offset: offset ? offset : 0
    });
  }

  function afterPopScroll() {
    preventScrollSectionChange = false;
  }

  function ContactValidationException(message) {
    this.message = message;
  }

  function validateContactEmail() {
    var email = $contactEmail.val();

    if (email.length < 3) {
      throw new ContactValidationException();
    }
  }

  function validateContactMessage() {
    var message = $contactMessage.val();

    if (message.length < contactMessageMin) {
      throw new ContactValidationException('Your message is a tad too short! Please enter at least ' +
                                            contactMessageMin + ' characters.');
    }

    if (message.length > contactMessageMax) {
      throw new ContactValidationException('Your message is a bit too long! Please keep it under ' +
                                            contactMessageMin + ' characters.');
    }
  }

  function toggleErrorTip(mode, $el, text) {
    var $parent = $el.parent();

    if (mode === 'show') {
      $parent.removeClass('valid').addClass('error');
      $parent.find('.error-tip').text(text);
    } else if (mode === 'hide') {
      $parent.removeClass('error');
    }
  }

  /**********************************
   * Events
   **********************************/

  $window.on('scroll', function(e, force) {
    var currentY = getScrollPos();
    //Only update on vertical scroll change
    if (prevY !== currentY || force) {
      var viewHeight = document.documentElement.clientHeight;

      if (!preventScrollSectionChange && !initialLoad) {
        //Change url if a new section has the majority of screen height
        $sections.each(function (i, el) {
          var $el = $(el);
          var section = $el.data('section');
          if (inViewport($el) > viewHeight / 2 && currentSection !== section) {
            preventScrollSectionAnim = true;
            gotoPage(section);
          }
        });
      }

      //Toggle a dark bg on nav
      if (currentY >= $introMiddle.offset().top && !$mainNav.hasClass('has-bg')) {
        $mainNav.addClass('has-bg');
      } else if (currentY < $introMiddle.offset().top && $mainNav.hasClass('has-bg')) {
        $mainNav.removeClass('has-bg');
      }

      //Show logo in nav if we scrolled passed the one in intro
      if (currentY >= $introLogo.offset().top + $introLogo.height() && !$mainNav.hasClass('has-logo')) {
        $navLogo.attr('aria-hidden', false);
        $mainNav.addClass('has-logo');
      } else if (currentY < $introLogo.offset().top + $introLogo.height() && $mainNav.hasClass('has-logo')) {
        $navLogo.attr('aria-hidden', true);
        $mainNav.removeClass('has-logo');
      }

      //Don't need to animate intro particles if we can't see them!
      if (introParticles) {
        var visible = currentY < $introParticles.offset().top + $introParticles.height();
        if (!introParticles.active && visible) {
          particlesJS.resume(introParticles);
        } else if (introParticles.active && !visible) {
          particlesJS.pause(introParticles);
        }
      }

      //Same with the skills particles
      if (skillsParticles) {
        var visible = currentY + viewHeight > $skillsParticles.offset().top &&
                      currentY < $skillsParticles.offset().top + $skillsParticles.height();
        if (!skillsParticles.active && visible) {
          particlesJS.resume(skillsParticles);
        } else if (skillsParticles.active && !visible) {
          particlesJS.pause(skillsParticles);
        }
      }

      //Handle elements that have scrolled into view
      var s = scrollInElements.length;
      while (s--) {
        var scrollIn = scrollInElements[s];
        if (currentY + viewHeight >= $body.height() || isVisible(currentY, viewHeight, scrollIn.$el, scrollIn.offset)) {
          scrollIn.$el.addClass('active');
          scrollInElements.splice(scrollInElements.indexOf(scrollIn), 1);
        }
      }

      prevY = currentY;
    }
  });

  $('a[href^="/"]').not('[href*="."]').not('[target="_blank"]').click(function() {
    gotoPage($(this).attr('href').substring(1));
    return false;
  });

  $window.on('popstate', function() {
    var page = history.state && history.state.page ? history.state.page : location.pathname.substring(1);
    previousSection = currentSection;
    currentSection = page !== '' ? page : 'home';

    var $target = getSectionElement(currentSection);
    activateNavItem();

    if (!preventScrollSectionAnim) {
      if ($target.length > 0) {
        preventScrollSectionChange = true;

        if (initialLoad) {
          if ($target.offset().top) {
              window.scrollTo(0, $target.offset().top);
          }
          afterPopScroll();

        } else {
          var scrollAnimComplete = false;
          scrollTo($target.offset().top, 250, function() {
            if (!scrollAnimComplete) {
              scrollAnimComplete = true;
              afterPopScroll();
            }
          });
        }
      }
    }

    preventScrollSectionAnim = false;
  });

  //Contact form stuff

  $contactEmail.on('blur keypress', function(e) {
    var $parent = $contactEmail.parent();
    if (e.type === 'keypress' && !$parent.hasClass('error')) {
      return true;
    }

    try {
      validateContactEmail();
      toggleErrorTip('hide', $contactEmail);
      $parent.addClass('valid');
    } catch (e) {
      if (e instanceof ContactValidationException) {
        toggleErrorTip('show', $contactEmail, 'Please enter a valid email address.');
      }
    }
  });

  $contactMessage.on('blur keypress', function(e) {
    var $parent = $contactMessage.parent();
    if (e.type === 'keypress' && !$parent.hasClass('error')) {
      return true;
    }

    try {
      validateContactMessage();
      toggleErrorTip('hide', $contactMessage);
      $parent.addClass('valid');
    } catch (e) {
      if (e instanceof ContactValidationException) {
        toggleErrorTip('show', $contactMessage, e.message);
      }
    }
  });

  $contactForm.find('.alert').on('click', '.close', function() {
    $(this).closest('.alert').removeClass('active');
    $contactForm.removeClass('has-alert');
  });

  $sectionContact.find('form').on('submit', function(e) {
    e.preventDefault();
    var errors = false;

    try {
      validateContactEmail();
      toggleErrorTip('hide', $contactEmail);
    } catch (e) {
      if (e instanceof ContactValidationException) {
        errors = true;
        toggleErrorTip('show', $contactEmail, 'Please enter a valid email address.');
      }
    }

    try {
      validateContactMessage();
      toggleErrorTip('hide', $contactMessage);
    } catch (e) {
      if (e instanceof ContactValidationException) {
        errors = true;
        toggleErrorTip('show', $contactMessage, e.message);
      }
    }

    if (errors || contactSending) {
      return false;
    }

    //submit form via ajax
    contactSending = true;
    $contactForm.find('button[type="submit"]').attr('disabled', 'disabled').addClass('loading');

    $.ajax({
      type: 'POST',
      url: $contactForm.attr('action'),
      dataType: 'json',
      data: {
        ajax: true,
        name: $contactForm.find('#contact-name').val(),
        email: $contactEmail.val(),
        message: $contactMessage.val(),
        url: $contactForm.find('#contact-url').val()
      },
      complete: function() {
        //Ensure the form always unlocks regardless of error state
        $contactForm.find('button[type="submit"]').removeAttr('disabled').removeClass('loading');
        $contactForm.children('fieldset').removeClass('valid error');
        contactSending = false;
      },
      success: function(res) {
        if (res.error) {
          //Something broke on the server welp
          $contactForm.addClass('has-alert');
          $contactForm.find('.send-error').addClass('active')
            .find('.error-code').text(res.code ? '(Err: ' + res.code + ')' : '');

        } else if (res.errors) {
          //User got validation errors (bypassed the html/js rules?)
          $.each(res.errors, function (type, message) {
            if (type === 'email') {
              toggleErrorTip('show', $contactEmail, message);
            } else if (type === 'message') {
              toggleErrorTip('show', $contactMessage, message);
            }
          });

        } else if (res.success) {
          //Message actually went through!
          $contactForm.get(0).reset();
          $contactForm.addClass('has-alert');
          $contactForm.find('.send-success').addClass('active');
        }
      },
      error: function() {
        //Something broke on the server welp
        $contactForm.addClass('has-alert');
        $contactForm.find('.send-error').addClass('active')
          .find('.error-code').text('(Err: 99)');
      }
    });
  });

  /**********************************
   * Scroll-in visibility effects
   **********************************/

  $sections.not($sectionIntro).find('header .title').each(function(i, el) {
    registerScrollInEvent($(el));
  });

  $sectionWork.find('.showcase .item .logo').each(function(i, el) {
    registerScrollInEvent($(el));
  });

  $sectionWork.find('.showcase .item .info').each(function(i, el) {
    registerScrollInEvent($(el));
  });

  $sectionSkills.find('article > p, .resume').each(function(i, el) {
    registerScrollInEvent($(el));
  });

  $sectionSkills.find('article > ul li').each(function(i, el) {
    registerScrollInEvent($(el));
  });

  $sectionContact.find('article > p, form button[type="submit"]').each(function(i, el) {
    registerScrollInEvent($(el));
  });

  $sectionContact.find('form > fieldset').each(function(i, el) {
    registerScrollInEvent($(el), ($(el).height() / 4) * -1);
  });

  $sectionContact.find('.social a').each(function(i, el) {
    registerScrollInEvent($(el));
  });

  /**********************************
   * Init
   **********************************/

  var introParticles = particlesJS('intro-particles', window.introParticlesConf);
  var skillsParticles = particlesJS('skills-particles', window.skillsParticlesConf);

  //ghetto do-it-yourself ladda.js
  $('.dh-btn.loading-enabled').each(function(i, el) {
    var $el = $(el);
    var html = $el.html();
    $el.empty();
    $el.append('' + html + '')
      .append('');
  });

  $sectionIntro.addClass('active');

  $window.trigger('popstate');
  $window.trigger('scroll');

  initialLoad = false;
});<|endoftext|>


<commit_before>'use strict';
const Generator = require('yeoman-generator');
const _ = require('lodash');
const yaml = require('js-yaml');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const glob = require('glob');

module.exports = class extends Generator {
  initializing() {
    this.option('enable-cors');

    this.currentDir = path.basename(process.cwd());
    this.props = {};

    // Find conf file
    let locate = () => {
      let files = glob.sync('{./,../,../../,../../../}/serverless.yml', {
        nodir: true,
        realpath: true
      });

      if (!files) {
        this.log.error('serverless.yml NOT FOUND, for overwrite go inside a project.');
      }

      return files ? files[0] : './';
    };

    this.configFile = locate();
  }

  prompting() {
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Resource name',
        filter: _.kebabCase.toLower,
        validate: value => {
          return !_.isEmpty(value);
        }
      },
      {
        type: 'list',
        name: 'nested',
        message: 'What kind of design?',
        choices: answers => {
          return [
            `Normal "/${answers.name}"`,
            `By id "/${answers.name}/{id}"`,
            `Nested "/${this.currentDir}/{id}/${answers.name}"`
          ];
        }
      },
      {
        type: 'list',
        name: 'method',
        message: 'Which HTTP method?',
        choices: answers => {
          let disabled = answers.nested.match(/By id/);
          return [
            {name: 'GET', disabled: false},
            {name: 'POST', disabled},
            {name: 'PUT', disabled: false},
            {name: 'DELETE', disabled: false}
          ];
        },
        filter: _.toLower
      },
      {
        type: 'input',
        name: 'description',
        message: 'Your function description',
        default: answers => {
          let name = (answers.nested.match(/Nested/)) ? `${this.currentDir} ${answers.name}` : answers.name;
          let byId = (answers.nested.match(/By id/)) ? ' by id' : '';
          return `${_.capitalize(answers.method)} ${name}${byId}`;
        }
      }
    ]).then(answers => {
      this.props = answers;
    });
  }

  writing() {
    let method = `${this.props.method}`;
    let handler = 'functions/';
    let filename = method;
    let lambda = `${method}`;
    let dest = handler;

    // If file name just when GET by id
    if (method === 'get' && this.props.nested.match(/By id/)) {
      filename = 'id';
    }

    // Build the configuration file
    if (this.props.nested.match(/By id/)) {
      dest += this.props.name;
    } else if (this.props.nested.match(/Nested/)) {
      // Nested
      lambda += `-${this.currentDir}`;
      handler += `${this.currentDir}/`;
      dest = this.props.name;
    } else {
      dest += this.props.name;
    }

    // Final name of lambda function & handler
    handler += this.props.name;
    lambda += `-${this.props.name}`;
    if (method === 'get' && this.props.nested.match(/By id/)) {
      lambda += '-id';
    }
    handler += `/${filename}.handler`;

    try {
      // Start overwrite config file
      let serverless = yaml.safeLoad(fs.readFileSync(this.configFile, 'utf8'));
      let http = {
        method: _.toUpper(this.props.method),
        path: this.props.name
      };

      // If enable-cors
      if (this.options.enableCors) {
        http.cors = true;
      }

      serverless.functions[lambda] = {
        name: `${serverless.service}-${lambda}`,
        description: this.props.description,
        handler,
        events: [
          {
            http
          }
        ]
      };

      fs.writeFile(
        this.configFile,
        yaml.safeDump(serverless),
        () => this.log.ok(`Function "${lambda}" generated successfully`)
      );
    } catch (ex) {
      this.log.error('Could not read/write serverless.yml file.');
    }

    // Create function handler
    mkdirp(dest);
    this.fs.copyTpl(
      this.templatePath('handler.js'),
      this.destinationPath(`${dest}/${filename}.js`),
      {
        lambda,
        method,
        path: (this.props.nested.match(/Nested/)) ? '../../..' : '../..'
      }
    );
  }
};
<commit_msg>Enable cors by default<commit_after>'use strict';
const Generator = require('yeoman-generator');
const _ = require('lodash');
const yaml = require('js-yaml');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const glob = require('glob');

module.exports = class extends Generator {
  initializing() {
    this.currentDir = path.basename(process.cwd());
    this.props = {};

    // Find conf file
    let locate = () => {
      let files = glob.sync('{./,../,../../,../../../}/serverless.yml', {
        nodir: true,
        realpath: true
      });

      if (!files) {
        this.log.error('serverless.yml NOT FOUND, for overwrite go inside a project.');
      }

      return files ? files[0] : './';
    };

    this.configFile = locate();
  }

  prompting() {
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Resource name',
        filter: _.kebabCase.toLower,
        validate: value => {
          return !_.isEmpty(value);
        }
      },
      {
        type: 'list',
        name: 'nested',
        message: 'What kind of design?',
        choices: answers => {
          return [
            `Normal "/${answers.name}"`,
            `By id "/${answers.name}/{id}"`,
            `Nested "/${this.currentDir}/{id}/${answers.name}"`
          ];
        }
      },
      {
        type: 'list',
        name: 'method',
        message: 'Which HTTP method?',
        choices: answers => {
          let disabled = answers.nested.match(/By id/);
          return [
            {name: 'GET', disabled: false},
            {name: 'POST', disabled},
            {name: 'PUT', disabled: false},
            {name: 'DELETE', disabled: false}
          ];
        },
        filter: _.toLower
      },
      {
        type: 'input',
        name: 'description',
        message: 'Your function description',
        default: answers => {
          let name = (answers.nested.match(/Nested/)) ? `${this.currentDir} ${answers.name}` : answers.name;
          let byId = (answers.nested.match(/By id/)) ? ' by id' : '';
          return `${_.capitalize(answers.method)} ${name}${byId}`;
        }
      }
    ]).then(answers => {
      this.props = answers;
    });
  }

  writing() {
    let method = `${this.props.method}`;
    let handler = 'functions/';
    let filename = method;
    let lambda = `${method}`;
    let dest = handler;

    // If file name just when GET by id
    if (method === 'get' && this.props.nested.match(/By id/)) {
      filename = 'id';
    }

    // Build the configuration file
    if (this.props.nested.match(/By id/)) {
      dest += this.props.name;
    } else if (this.props.nested.match(/Nested/)) {
      // Nested
      lambda += `-${this.currentDir}`;
      handler += `${this.currentDir}/`;
      dest = this.props.name;
    } else {
      dest += this.props.name;
    }

    // Final name of lambda function & handler
    handler += this.props.name;
    lambda += `-${this.props.name}`;
    if (method === 'get' && this.props.nested.match(/By id/)) {
      lambda += '-id';
    }
    handler += `/${filename}.handler`;

    try {
      // Start overwrite config file
      let serverless = yaml.safeLoad(fs.readFileSync(this.configFile, 'utf8'));
      let http = {
        method: _.toUpper(this.props.method),
        path: this.props.name
      };

      // Enable cors by default
      http.cors = true;

      serverless.functions[lambda] = {
        name: `${serverless.service}-${lambda}`,
        description: this.props.description,
        handler,
        events: [
          {
            http
          }
        ]
      };

      fs.writeFile(
        this.configFile,
        yaml.safeDump(serverless),
        () => this.log.ok(`Function "${lambda}" generated successfully`)
      );
    } catch (ex) {
      this.log.error('Could not read/write serverless.yml file.');
    }

    // Create function handler
    mkdirp(dest);
    this.fs.copyTpl(
      this.templatePath('handler.js'),
      this.destinationPath(`${dest}/${filename}.js`),
      {
        lambda,
        method,
        path: (this.props.nested.match(/Nested/)) ? '../../..' : '../..'
      }
    );
  }
};
<|endoftext|>


<commit_before>/**
 * Internal dependencies
 */
import { sendAnalyticsTrackingEvent } from '../';

describe( 'sendAnalyticsTrackingEvent', () => {
	it( 'sends a tracking event', () => {
		// eslint-disable-next-line no-undef
		global.googlesitekitTrackingEnabled = true;
		// eslint-disable-next-line no-undef
		global.googlesitekit.setup.isFirstAdmin = true;

		const expected = '{"type":"event","name":"name","sendto":{"event_category":"category","event_label":"label","event_value":"value","dimension1":"","dimension2":"true"}}';
		const actual = JSON.stringify( sendAnalyticsTrackingEvent( 'category', 'name', 'label', 'value' ) );

		expect( actual ).toStrictEqual( expected );
	} );
} );
<commit_msg>Update failing Jest test to use new global.<commit_after>/**
 * Internal dependencies
 */
import { sendAnalyticsTrackingEvent } from '../';

describe( 'sendAnalyticsTrackingEvent', () => {
	it( 'sends a tracking event', () => {
		// eslint-disable-next-line no-undef
		global.googlesitekitTrackingEnabled = true;
		// eslint-disable-next-line no-undef
		global._googlesitekitBase = {
			referenceSiteURL: 'https://www.example.com/',
			userIDHash: 'a1b2c3',
			trackingID: 'UA-12345678-1',
			isFirstAdmin: true,
		};

		const expected = '{"type":"event","name":"name","sendto":{"send_to":"UA-12345678-1","event_category":"category","event_label":"label","event_value":"value","dimension1":"https://www.example.com","dimension2":"true","dimension3":"a1b2c3"}}';
		const actual = JSON.stringify( sendAnalyticsTrackingEvent( 'category', 'name', 'label', 'value' ) );

		expect( actual ).toStrictEqual( expected );
	} );
} );
<|endoftext|>


<commit_before>import React from 'react-native';
var {
  Component,
  StyleSheet,
  View,
  Text
} = React

var stars = require('./Components/StarData/Stars.json')
var star = stars[Math.floor(Math.random()*stars.length)]

var Stars = React.createClass ({
  getInitialState: function() {
    return stars[Math.floor(Math.random()*stars.length)];
  },
  tick: function() {
    this.setState(stars[Math.floor(Math.random()*stars.length)])
  },
  componentDidMount: function() {
    this.interval = setInterval(this.tick, 1000);
  },
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>Random Star!</Text>
        <Text style={styles.text}>id: {this.state['ID']}</Text>
        <Text style={styles.text}>Proper Name: {this.state['Proper Name']}</Text>
        <Text style={styles.text}>Right ascension: {this.state['Right Ascension']}</Text>
        <Text style={styles.text}>Declination: {this.state['Declination']}</Text>
        <Text style={styles.text}>Spectrum: {this.state['Spectrum']}</Text>
        <Text style={styles.text}>Constellation: {this.state['Constellation']}</Text>
      </View>
    );
  }
})

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  text: {
    color: 'blue',
    fontFamily: 'Helvetica',
    fontSize: 18,
    textAlign: 'center',
    marginHorizontal: 10,
    marginVertical: 5
  },
});

module.exports = Stars
<commit_msg>set higher ticker interval for random stars<commit_after>import React from 'react-native';
var {
  Component,
  StyleSheet,
  View,
  Text
} = React

var stars = require('./Components/StarData/Stars.json')
var star = stars[Math.floor(Math.random()*stars.length)]

var Stars = React.createClass ({
  getInitialState: function() {
    return stars[Math.floor(Math.random()*stars.length)];
  },
  tick: function() {
    this.setState(stars[Math.floor(Math.random()*stars.length)])
  },
  componentDidMount: function() {
    this.interval = setInterval(this.tick, 4000);
  },
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>Random Star!</Text>
        <Text style={styles.text}>id: {this.state['ID']}</Text>
        <Text style={styles.text}>Proper Name: {this.state['Proper Name']}</Text>
        <Text style={styles.text}>Right ascension: {this.state['Right Ascension']}</Text>
        <Text style={styles.text}>Declination: {this.state['Declination']}</Text>
        <Text style={styles.text}>Spectrum: {this.state['Spectrum']}</Text>
        <Text style={styles.text}>Constellation: {this.state['Constellation']}</Text>
      </View>
    );
  }
})

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  text: {
    color: 'blue',
    fontFamily: 'Helvetica',
    fontSize: 18,
    textAlign: 'center',
    marginHorizontal: 10,
    marginVertical: 5
  },
});

module.exports = Stars
<|endoftext|>


<commit_before>import '../../../css/browser/side-bar'

import React, { PropTypes } from 'react'

import { connect } from 'react-redux'
import { FormattedMessage as T } from 'react-intl'
import cx from 'classnames'

class SideBarContext extends React.Component {
  render () {
    console.log('SideBarContext')
    return <T id="sidebar.context-title" />
  }
}

SideBarContext.propTypes = {
  serverUrl: PropTypes.string.isRequired,
  corpusId: PropTypes.string.isRequired,
  webentity: PropTypes.object.isRequired
}

const mapStateToProps = ({ ui }) => ({ // eslint-disable-line
})

export default connect(mapStateToProps, {
})(SideBarContext)
<commit_msg>fix(): remove forgotten console.log<commit_after>import '../../../css/browser/side-bar'

import React, { PropTypes } from 'react'

import { connect } from 'react-redux'
import { FormattedMessage as T } from 'react-intl'
import cx from 'classnames'

class SideBarContext extends React.Component {
  render () {
    return <T id="sidebar.context-title" />
  }
}

SideBarContext.propTypes = {
  serverUrl: PropTypes.string.isRequired,
  corpusId: PropTypes.string.isRequired,
  webentity: PropTypes.object.isRequired
}

const mapStateToProps = ({ ui }) => ({ // eslint-disable-line
})

export default connect(mapStateToProps, {
})(SideBarContext)
<|endoftext|>


<commit_before>import React from 'react';

import Dropdown from '../../../src/Dropdown/Dropdown.js';

class DropdownExample extends React.Component {

  constructor() {
    super();
    this.dropdownSelected = 'foo';
    this.onItemSelection = this.onItemSelection.bind(this);
  }

  onItemSelection(item) {
    this.dropdownSelected = item.html.toLowerCase();
    this.forceUpdate();
  }

  render() {
    let dropdownItems = [
      {
        html: 'Foo',
        id: 'foo'
      },
      {
        html: 'Bar',
        id: 'bar'
      },
      {
        html: 'Baz',
        id: 'baz'
      },
      {
        html: 'A tiny whale',
        id: 'tiny-whale'
      }
    ];

    let styledItemsList = [
      {
        html:

            Foo
            Recommended
          ,
        id: 'foo',
        selectedHtml: 'Foo'
      },
      {
        html:

              Bar
              1

          ,
        id: 'bar',
        selectedHtml:

              Bar
              1

      },
      {
        className: 'dropdown-menu-divider',
        id: 'divider-a',
        selectable: false
      },
      {
        html:

              Baz
              2

          ,
        id: 'baz',
        selectedHtml:

              Baz
              2

      }
    ];

    return (

                  Dropdowns

                  Use custom html elements within dropdowns, style with dividers, and add callbacks.

Here is a simple dropdown.

                    <Dropdown buttonClassName="button dropdown-toggle"
                      dropdownMenuClassName="dropdown-menu"
                      dropdownMenuListClassName="dropdown-menu-list"
                      items={dropdownItems}
                      selectedID="foo"
                      transition={true}
                      wrapperClassName="dropdown" />

{`import Dropdown from 'Dropdown.js';

[...]

let dropdownItems = [
  {
    html: 'Foo',f
    id: 'foo'
  },
  {
    html: 'Bar',
    id: 'bar'
  },
  {
    html: 'Baz',
    id: 'baz'
  },
  {
    html: 'A tiny whale',
    id: 'whale'
  }
];

<Dropdown buttonClassName="button dropdown-toggle"
  dropdownMenuClassName="dropdown-menu"
  dropdownMenuListClassName="dropdown-menu-list"
  items={dropdownItems}
  selectedID="foo"
  transition={true}
  wrapperClassName="dropdown" />
`}

                  Callbacks

                  Use the onItemSelection attribute to add
                  callbacks to item selection.

You have selected {this.dropdownSelected}.

                    <Dropdown buttonClassName="button dropdown-toggle"
                      dropdownMenuClassName="dropdown-menu"
                      dropdownMenuListClassName="dropdown-menu-list"
                      items={dropdownItems}
                      onItemSelection={this.onItemSelection}
                      selectedID="foo"
                      transition={true}
                      wrapperClassName="dropdown" />

{`import Dropdown from 'Dropdown.js';

[...]

onItemSelection(item) {
    this.dropdownSelected = item.html.toLowerCase();
}

[...]

let dropdownItems = [
  {
    html: 'Foo',
    id: 'foo'
  },
  {
    html: 'Bar',
    id: 'bar'
  },
  {
    html: 'Baz',
    id: 'baz'
  },
  {
    html: 'A tiny whale',
    id: 'whale'
  }
];

[...]

You have selected {this.dropdownSelected}.

<Dropdown buttonClassName="button dropdown-toggle"
  dropdownMenuClassName="dropdown-menu"
  dropdownMenuListClassName="dropdown-menu-list"
  items={dropdownItems}
  onItemSelection={this.onItemSelection}
  selectedID="foo"
  transition={true}
  wrapperClassName="dropdown" />
`}

                Custom HTML elements

Here is a dropdown with custom HTML elements.

                    <Dropdown buttonClassName="button dropdown-toggle"
                      dropdownMenuClassName="dropdown-menu"
                      dropdownMenuListClassName="dropdown-menu-list"
                      items={styledItemsList}
                      selectedID="foo"
                      transition={true}
                      wrapperClassName="dropdown" />

{`import Dropdown from 'Dropdown.js';

[...]

let styledItemsList = [
  {
    html:

        Foo
        Recommended
      ,
    id: 'foo',
    selectedHtml: 'Foo'
  },
  {
    html:

          Bar
          1

      ,
    id: 'bar',
    selectedHtml:

          Bar
          1

  },
  {
    className: 'dropdown-menu-divider',
    id: 'divider-a',
    selectable: false
  },
  {
    html:

          Baz
          2

      ,
    id: 'baz',
    selectedHtml:

          Baz
          2

  }
];

[...]

<Dropdown buttonClassName="button dropdown-toggle"
  dropdownMenuClassName="dropdown-menu"
  dropdownMenuListClassName="dropdown-menu-list"
  items={styledItemsList}
  selectedID="foo"
  transition={true}
  wrapperClassName="dropdown" />
`}

    );
  }
}

React.render(<DropdownExample />, document.getElementById('dropdown'));
<commit_msg>add links to repo<commit_after>import React from 'react';

import Dropdown from '../../../src/Dropdown/Dropdown.js';

class DropdownExample extends React.Component {

  constructor() {
    super();
    this.dropdownSelected = 'foo';
    this.onItemSelection = this.onItemSelection.bind(this);
  }

  onItemSelection(item) {
    this.dropdownSelected = item.html.toLowerCase();
    this.forceUpdate();
  }

  render() {
    let dropdownItems = [
      {
        html: 'Foo',
        id: 'foo'
      },
      {
        html: 'Bar',
        id: 'bar'
      },
      {
        html: 'Baz',
        id: 'baz'
      },
      {
        html: 'A tiny whale',
        id: 'tiny-whale'
      }
    ];

    let styledItemsList = [
      {
        html:

            Foo
            Recommended
          ,
        id: 'foo',
        selectedHtml: 'Foo'
      },
      {
        html:

              Bar
              1

          ,
        id: 'bar',
        selectedHtml:

              Bar
              1

      },
      {
        className: 'dropdown-menu-divider',
        id: 'divider-a',
        selectable: false
      },
      {
        html:

              Baz
              2

          ,
        id: 'baz',
        selectedHtml:

              Baz
              2

      }
    ];

    return (

                  Dropdowns

                  Use custom html elements within dropdown menus and add callbacks. View the component source for all props and
                  customizations. To see the full context of these examples, view the example source.

Here is a simple dropdown.

                    <Dropdown buttonClassName="button dropdown-toggle"
                      dropdownMenuClassName="dropdown-menu"
                      dropdownMenuListClassName="dropdown-menu-list"
                      items={dropdownItems}
                      selectedID="foo"
                      transition={true}
                      wrapperClassName="dropdown" />

{`import Dropdown from 'Dropdown.js';

[...]

let dropdownItems = [
  {
    html: 'Foo',f
    id: 'foo'
  },
  {
    html: 'Bar',
    id: 'bar'
  },
  {
    html: 'Baz',
    id: 'baz'
  },
  {
    html: 'A tiny whale',
    id: 'whale'
  }
];

<Dropdown buttonClassName="button dropdown-toggle"
  dropdownMenuClassName="dropdown-menu"
  dropdownMenuListClassName="dropdown-menu-list"
  items={dropdownItems}
  selectedID="foo"
  transition={true}
  wrapperClassName="dropdown" />
`}

                  Callbacks

                  Use the onItemSelection attribute to add
                  callbacks to item selection.

You have selected {this.dropdownSelected}.

                    <Dropdown buttonClassName="button dropdown-toggle"
                      dropdownMenuClassName="dropdown-menu"
                      dropdownMenuListClassName="dropdown-menu-list"
                      items={dropdownItems}
                      onItemSelection={this.onItemSelection}
                      selectedID="foo"
                      transition={true}
                      wrapperClassName="dropdown" />

{`import Dropdown from 'Dropdown.js';

[...]

onItemSelection(item) {
    this.dropdownSelected = item.html.toLowerCase();
}

[...]

let dropdownItems = [
  {
    html: 'Foo',
    id: 'foo'
  },
  {
    html: 'Bar',
    id: 'bar'
  },
  {
    html: 'Baz',
    id: 'baz'
  },
  {
    html: 'A tiny whale',
    id: 'whale'
  }
];

[...]

You have selected {this.dropdownSelected}.

<Dropdown buttonClassName="button dropdown-toggle"
  dropdownMenuClassName="dropdown-menu"
  dropdownMenuListClassName="dropdown-menu-list"
  items={dropdownItems}
  onItemSelection={this.onItemSelection}
  selectedID="foo"
  transition={true}
  wrapperClassName="dropdown" />
`}

                Custom HTML elements

Use HTML to style dropdown elements.

Here is a styled dropdown.

                    <Dropdown buttonClassName="button dropdown-toggle"
                      dropdownMenuClassName="dropdown-menu"
                      dropdownMenuListClassName="dropdown-menu-list"
                      items={styledItemsList}
                      selectedID="foo"
                      transition={true}
                      wrapperClassName="dropdown" />

{`import Dropdown from 'Dropdown.js';

[...]

let styledItemsList = [
  {
    html:

        Foo
        Recommended
      ,
    id: 'foo',
    selectedHtml: 'Foo'
  },
  {
    html:

          Bar
          1

      ,
    id: 'bar',
    selectedHtml:

          Bar
          1

  },
  {
    className: 'dropdown-menu-divider',
    id: 'divider-a',
    selectable: false
  },
  {
    html:

          Baz
          2

      ,
    id: 'baz',
    selectedHtml:

          Baz
          2

  }
];

[...]

<Dropdown buttonClassName="button dropdown-toggle"
  dropdownMenuClassName="dropdown-menu"
  dropdownMenuListClassName="dropdown-menu-list"
  items={styledItemsList}
  selectedID="foo"
  transition={true}
  wrapperClassName="dropdown" />
`}

    );
  }
}

React.render(<DropdownExample />, document.getElementById('dropdown'));
<|endoftext|>


<commit_before>var luchTime = angular.module('lunchTime', [
	'ui.router'
])
.config(function($stateProvider, $urlRouterProvider) {
	$urlRouterProvider.otherwise("/");

	$stateProvider
		.state('home', {
			url: "/",
			templateUrl: "views/login.html"
		})
		.state('mapa', {
			url: "/mapa",
			templateUrl: "views/mapa.html",
			params: {'centerLat' : localPrincipal.latitude, 'centerLng' : localPrincipal.longitude},
			controller: 'MapController'
		})
		.state('user', {
			url: "/user",
			templateUrl: "views/user.html",
			controller: "UserController"
		});
})
.directive("setOnClick", function($compile){
  return {
    restrict: "A",
    link: function(scope, elm, attrs, ctrl)
    {
      elm.attr("ng-click", "addtogroup()");
      elm.removeAttr("set-on-click");
      $compile(elm)(scope);
    }
  };
})
.controller('LoginController', ['$scope', '$state', function ($scope, $state) {
	$scope.formData = {};
	$scope.processLogin = function (){
		//alert($scope.pass + " " + $scope.login);
		$state.go("mapa", {
			'centerLat': localPrincipal.latitude,
			'centerLng': localPrincipal.longitude
		});
	}
}])
.controller('MapController', ['$scope', '$state', '$stateParams', '$document', function($scope, $state, $stateParams, $document){

	$scope.meusdados = function (){
		$state.go("user", {});
	}

	var noPoi = [
 	{
 	    featureType: "poi",
 	    stylers: [
 	      { visibility: "off" }
 	    ]
 	  }
 	];

 	var mapOptions = {
 		center: new google.maps.LatLng($stateParams.centerLat, $stateParams.centerLng),
 		zoom: 17,
 		mapTypeId: google.maps.MapTypeId.ROADMAP,
 		mapTypeControl: false,
 		streetViewControl: true,
 		disableDefaultUI: true
 	};

 	$scope.map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

 	var primarker = new google.maps.Marker({
        map: $scope.map,
        position: new google.maps.LatLng(localPrincipal.latitude, localPrincipal.longitude),
        title: localPrincipal.nome,
        icon : "/lunchtime/img/localprincipal.png"
    });

 	google.maps.event.addListener(primarker, 'click', function(){
			$scope.infowindow.setContent("Grupo: " + localPrincipal.nome + "");
			$scope.infowindow.open($scope.map, primarker);
    });

 	$scope.markers = [];

 	$scope.infowindow = new google.maps.InfoWindow();

 	var createMarker = function(info){

 		var eixox = 1;
 		var eixoy = 136;
 		if(Number(info.votos[0]) > 1){
 			eixox = (Number(info.votos[0]) - 1) * 35 + 1;
 		}
 		if(info.votado != "") {
 			eixoy = 92;
 		}

 		var icon;

 		if(Number(info.votos[0]) > 0){
 			icon = new google.maps.MarkerImage("/lunchtime/img/mappin-sprite.png", new google.maps.Size(33, 42), new google.maps.Point(eixox, eixoy));
 		} else {
 			icon = new google.maps.MarkerImage("/lunchtime/img/mappin-blank.png", new google.maps.Size(33, 42));
 		}

 		var marker = new google.maps.Marker({
            map: $scope.map,
            position: new google.maps.LatLng(info.latitude, info.longitude),
            title: info.titulo,
            icon : icon,
 			animation : google.maps.Animation.DROP
        });

 		marker.titulo = info.titulo;
 		marker.categoria = info.categoria;
 		marker.endereco = info.endereco;
 		marker.extra = info.extra;
 		marker.votos = info.votos;
 		marker.votado = info.votado;
 		marker.quantidade = info.votos[0];
 		marker.ltmarker = true;

 		marker.content = "
" +
						"

" +
						"
" +
							"
" + info.titulo + "
" +
							"
" + info.endereco + "
" +
							"
" + info.extra + "
" +
						"
" +
						"
" +
							"
" +
								"" + info.votos + "" +
							"
" +
							"

" +
						"
" +
					"
";

 		google.maps.event.addListener(marker, 'click', function(){
 			$scope.infowindow.setContent(marker.content);
 			$scope.infowindow.open($scope.map, marker);
        });

        $scope.markers.push(marker);

 	}

    for (i = 0; i < locais.length; i++){
    	local = locais[i];
    	var info = {};
    	info.titulo = local.titulo;
    	info.endereco = local.endereco;
    	info.latitude = local.latitude;
    	info.longitude = local.longitude;
    	var infos = local.infos
    	info.extra = local.categoria;
    	info.categoria = local.categoria;
    	for (j = 0; j < infos.length; j++){
    		info.extra += ", " + infos[j];
    	}
    	info.extra += " - " + local.valor;
    	info.votos = local.votos[0] + " " + local.votos[1];
    	info.votado = local.votado;

        createMarker(info);
    }

    $scope.filtroLocais = function(item){
    	return Number(item.quantidade) > 0;
    }

    $scope.openInfoWindow = function(e, selectedMarker){
        e.preventDefault();
        google.maps.event.trigger(selectedMarker, 'click');
    }

 	$scope.coreToolBar = "red";

 	$scope.iconMenuClass = "close";
 	$scope.menuClass = "close";
 	$scope.iconSearchClass = "close";

 	$scope.iconSearchBackClass = "disable";
 	$scope.coresearchlist = "disable";

 	$scope.dummyDiv = "disable";

 	$scope.iconMenuClass = "enable";
 	$scope.iconMenuClass = "close";

 	$scope.title = "LunchTime"
 	$scope.coreTitle = "title-default";

 	$scope.menushow = function(){

 		if($scope.iconMenuClass == "close") {
 			$scope.iconMenuClass = "openmenu";
 	 	 	$scope.menuClass = "open";
 	 	 	$scope.iconSearchClass = "disable";
 	 	 	$scope.dummyDiv = "enable";
 	 	 	$scope.title = "Menu"
 		} else {
 			$scope.iconMenuClass = "close";
 	 	 	$scope.menuClass = "close";
 	 	 	$scope.iconSearchClass = "enable";
 	 	 	$scope.dummyDiv = "disable";
 	 	 	$scope.title = "LunchTime";
 		}
 	}

 	$scope.searchshow = function(){
 		if($scope.iconSearchBackClass == "disable") {
 			$scope.dummyDiv = "enable";
 			$scope.iconMenuClass = "disable";
 	 	 	$scope.iconSearchBackClass = "enable";
 	 	 	$scope.iconSearchInputClass = "enable";
 	 	 	$scope.iconSearchClass = "disable";
 	 	 	$scope.title = "";
 	 	 	$scope.coreToolBar = "white";
 	 	 	$scope.coreTitle = "title-search";
 	 	 	if($scope.searchtext) {
 	 	 		$scope.searchtext.titulo = "";
 	 	 	}
 		} else {
 			$scope.dummyDiv = "disable";
 			$scope.iconSearchBackClass = "disable";
 	 	 	$scope.iconSearchClass = "enable";
 	 	 	$scope.iconSearchInputClass = "disable";
 	 	 	$scope.iconMenuClass = "enable";
 	 	 	$scope.iconMenuClass = "close";
 	 	 	$scope.title = "LunchTime";
 	 	 	$scope.coreToolBar = "red";
 	 	 	$scope.coreTitle = "title-default";
 	 	 	$scope.coresearchlist = "disable";
 		}
 	}

 	$scope.changesearch = function(){
 		if($scope.searchtext.titulo != ""){
 			$scope.coresearchlist = "enable";
 		} else {
 			$scope.coresearchlist = "disable";
 		}

 	}

 	google.maps.event.addListener($scope.map, 'mousedown', function (e) {
 		$scope.mousedownx = e.pixel.x;
 		$scope.mousedowny = e.pixel.y;
 		$scope.mousedownstart = new Date().getTime();
 	});

 	google.maps.event.addListener($scope.map, 'mouseup', function (e) {
 		if(e.pixel.x == $scope.mousedownx && e.pixel.y == $scope.mousedowny){
 			var dif = new Date().getTime() - $scope.mousedownstart;
 			//300ms
 			if(dif > 300) {
 				console.log(e.latLng.k + " " + e.latLng.D);
 			}
 		}
 	});

 	$scope.addtogroup = function(){
 		console.log("link clicked");
 	}

}])
.controller('UserController', ['$scope', '$state', '$stateParams', '$document', function($scope, $state, $stateParams, $document){
	$scope.title = "Meus dados";
	$scope.coreToolBar = "blue";

	$scope.name = "Nome e Sobrenome";
	$scope.login = "login para login :P";
	$scope.email = "email@emailqualquer.com";

	$scope.backtomap = function (){
		$state.go("mapa", {
			'centerLat': localPrincipal.latitude,
			'centerLng': localPrincipal.longitude
		});
	}
}]);

function outaddtogroup(){
	angular.element(document.getElementById('lunchTimeApp')).scope().addtogroup();
}

function fixInfoWindow(){
 	var set = google.maps.InfoWindow.prototype.set;
 	google.maps.InfoWindow.prototype.set = function (key, val) {
		var self = this;
        if(key === "map") {
        	if (!this.anchor) {
        		var link = angular.element("Adicionar ao grupo");
        		var divlist = angular.element(this.content).find("div");
        		var gmrev;
        		for(var i = 0; i < divlist.length; i++) {
        			if(angular.element(divlist[i]).hasClass("gm-rev")) {
        				gmrev = divlist[i];
        				break;
        			}
        		}
                angular.element(gmrev).append(angular.element("

").append(link));
        	}
        }
        set.apply(this, arguments);
 	}
}<commit_msg>boa pratica<commit_after>(function(){

	var luchTime = angular.module('lunchTime', [
		'ui.router'
	])
	.config(function($stateProvider, $urlRouterProvider) {
		$urlRouterProvider.otherwise("/");

		$stateProvider
			.state('home', {
				url: "/",
				templateUrl: "views/login.html"
			})
			.state('mapa', {
				url: "/mapa",
				templateUrl: "views/mapa.html",
				params: {'centerLat' : localPrincipal.latitude, 'centerLng' : localPrincipal.longitude},
				controller: 'MapController'
			})
			.state('user', {
				url: "/user",
				templateUrl: "views/user.html",
				controller: "UserController"
			});
	})
	.directive("setOnClick", function($compile){
	  return {
	    restrict: "A",
	    link: function(scope, elm, attrs, ctrl)
	    {
	      elm.attr("ng-click", "addtogroup()");
	      elm.removeAttr("set-on-click");
	      $compile(elm)(scope);
	    }
	  };
	})
	.controller('LoginController', ['$scope', '$state', function ($scope, $state) {
		$scope.formData = {};
		$scope.processLogin = function (){
			//alert($scope.pass + " " + $scope.login);
			$state.go("mapa", {
				'centerLat': localPrincipal.latitude,
				'centerLng': localPrincipal.longitude
			});
		}
	}])
	.controller('MapController', ['$scope', '$state', '$stateParams', '$document', function($scope, $state, $stateParams, $document){

		$scope.meusdados = function (){
			$state.go("user", {});
		}

		var noPoi = [
	 	{
	 	    featureType: "poi",
	 	    stylers: [
	 	      { visibility: "off" }
	 	    ]
	 	  }
	 	];

	 	var mapOptions = {
	 		center: new google.maps.LatLng($stateParams.centerLat, $stateParams.centerLng),
	 		zoom: 17,
	 		mapTypeId: google.maps.MapTypeId.ROADMAP,
	 		mapTypeControl: false,
	 		streetViewControl: true,
	 		disableDefaultUI: true
	 	};

	 	$scope.map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

	 	var primarker = new google.maps.Marker({
	        map: $scope.map,
	        position: new google.maps.LatLng(localPrincipal.latitude, localPrincipal.longitude),
	        title: localPrincipal.nome,
	        icon : "/lunchtime/img/localprincipal.png"
	    });

	 	google.maps.event.addListener(primarker, 'click', function(){
				$scope.infowindow.setContent("Grupo: " + localPrincipal.nome + "");
				$scope.infowindow.open($scope.map, primarker);
	    });

	 	$scope.markers = [];

	 	$scope.infowindow = new google.maps.InfoWindow();

	 	var createMarker = function(info){

	 		var eixox = 1;
	 		var eixoy = 136;
	 		if(Number(info.votos[0]) > 1){
	 			eixox = (Number(info.votos[0]) - 1) * 35 + 1;
	 		}
	 		if(info.votado != "") {
	 			eixoy = 92;
	 		}

	 		var icon;

	 		if(Number(info.votos[0]) > 0){
	 			icon = new google.maps.MarkerImage("/lunchtime/img/mappin-sprite.png", new google.maps.Size(33, 42), new google.maps.Point(eixox, eixoy));
	 		} else {
	 			icon = new google.maps.MarkerImage("/lunchtime/img/mappin-blank.png", new google.maps.Size(33, 42));
	 		}

	 		var marker = new google.maps.Marker({
	            map: $scope.map,
	            position: new google.maps.LatLng(info.latitude, info.longitude),
	            title: info.titulo,
	            icon : icon,
	 			animation : google.maps.Animation.DROP
	        });

	 		marker.titulo = info.titulo;
	 		marker.categoria = info.categoria;
	 		marker.endereco = info.endereco;
	 		marker.extra = info.extra;
	 		marker.votos = info.votos;
	 		marker.votado = info.votado;
	 		marker.quantidade = info.votos[0];
	 		marker.ltmarker = true;

	 		marker.content = "
" +
							"

" +
							"
" +
								"
" + info.titulo + "
" +
								"
" + info.endereco + "
" +
								"
" + info.extra + "
" +
							"
" +
							"
" +
								"
" +
									"" + info.votos + "" +
								"
" +
								"

" +
							"
" +
						"
";

	 		google.maps.event.addListener(marker, 'click', function(){
	 			$scope.infowindow.setContent(marker.content);
	 			$scope.infowindow.open($scope.map, marker);
	        });

	        $scope.markers.push(marker);

	 	}

	    for (i = 0; i < locais.length; i++){
	    	local = locais[i];
	    	var info = {};
	    	info.titulo = local.titulo;
	    	info.endereco = local.endereco;
	    	info.latitude = local.latitude;
	    	info.longitude = local.longitude;
	    	var infos = local.infos
	    	info.extra = local.categoria;
	    	info.categoria = local.categoria;
	    	for (j = 0; j < infos.length; j++){
	    		info.extra += ", " + infos[j];
	    	}
	    	info.extra += " - " + local.valor;
	    	info.votos = local.votos[0] + " " + local.votos[1];
	    	info.votado = local.votado;

	        createMarker(info);
	    }

	    $scope.filtroLocais = function(item){
	    	return Number(item.quantidade) > 0;
	    }

	    $scope.openInfoWindow = function(e, selectedMarker){
	        e.preventDefault();
	        google.maps.event.trigger(selectedMarker, 'click');
	    }

	 	$scope.coreToolBar = "red";

	 	$scope.iconMenuClass = "close";
	 	$scope.menuClass = "close";
	 	$scope.iconSearchClass = "close";

	 	$scope.iconSearchBackClass = "disable";
	 	$scope.coresearchlist = "disable";

	 	$scope.dummyDiv = "disable";

	 	$scope.iconMenuClass = "enable";
	 	$scope.iconMenuClass = "close";

	 	$scope.title = "LunchTime"
	 	$scope.coreTitle = "title-default";

	 	$scope.menushow = function(){

	 		if($scope.iconMenuClass == "close") {
	 			$scope.iconMenuClass = "openmenu";
	 	 	 	$scope.menuClass = "open";
	 	 	 	$scope.iconSearchClass = "disable";
	 	 	 	$scope.dummyDiv = "enable";
	 	 	 	$scope.title = "Menu"
	 		} else {
	 			$scope.iconMenuClass = "close";
	 	 	 	$scope.menuClass = "close";
	 	 	 	$scope.iconSearchClass = "enable";
	 	 	 	$scope.dummyDiv = "disable";
	 	 	 	$scope.title = "LunchTime";
	 		}
	 	}

	 	$scope.searchshow = function(){
	 		if($scope.iconSearchBackClass == "disable") {
	 			$scope.dummyDiv = "enable";
	 			$scope.iconMenuClass = "disable";
	 	 	 	$scope.iconSearchBackClass = "enable";
	 	 	 	$scope.iconSearchInputClass = "enable";
	 	 	 	$scope.iconSearchClass = "disable";
	 	 	 	$scope.title = "";
	 	 	 	$scope.coreToolBar = "white";
	 	 	 	$scope.coreTitle = "title-search";
	 	 	 	if($scope.searchtext) {
	 	 	 		$scope.searchtext.titulo = "";
	 	 	 	}
	 		} else {
	 			$scope.dummyDiv = "disable";
	 			$scope.iconSearchBackClass = "disable";
	 	 	 	$scope.iconSearchClass = "enable";
	 	 	 	$scope.iconSearchInputClass = "disable";
	 	 	 	$scope.iconMenuClass = "enable";
	 	 	 	$scope.iconMenuClass = "close";
	 	 	 	$scope.title = "LunchTime";
	 	 	 	$scope.coreToolBar = "red";
	 	 	 	$scope.coreTitle = "title-default";
	 	 	 	$scope.coresearchlist = "disable";
	 		}
	 	}

	 	$scope.changesearch = function(){
	 		if($scope.searchtext.titulo != ""){
	 			$scope.coresearchlist = "enable";
	 		} else {
	 			$scope.coresearchlist = "disable";
	 		}

	 	}

	 	google.maps.event.addListener($scope.map, 'mousedown', function (e) {
	 		$scope.mousedownx = e.pixel.x;
	 		$scope.mousedowny = e.pixel.y;
	 		$scope.mousedownstart = new Date().getTime();
	 	});

	 	google.maps.event.addListener($scope.map, 'mouseup', function (e) {
	 		if(e.pixel.x == $scope.mousedownx && e.pixel.y == $scope.mousedowny){
	 			var dif = new Date().getTime() - $scope.mousedownstart;
	 			//300ms
	 			if(dif > 300) {
	 				console.log(e.latLng.k + " " + e.latLng.D);
	 			}
	 		}
	 	});

	 	$scope.addtogroup = function(){
	 		console.log("link clicked");
	 	}

	}])
	.controller('UserController', ['$scope', '$state', '$stateParams', '$document', function($scope, $state, $stateParams, $document){
		$scope.title = "Meus dados";
		$scope.coreToolBar = "blue";

		$scope.name = "Nome e Sobrenome";
		$scope.login = "login para login :P";
		$scope.email = "email@emailqualquer.com";

		$scope.backtomap = function (){
			$state.go("mapa", {
				'centerLat': localPrincipal.latitude,
				'centerLng': localPrincipal.longitude
			});
		}
	}]);

})();

function outaddtogroup(){
	angular.element(document.getElementById('lunchTimeApp')).scope().addtogroup();
}

function fixInfoWindow(){
 	var set = google.maps.InfoWindow.prototype.set;
 	google.maps.InfoWindow.prototype.set = function (key, val) {
		var self = this;
        if(key === "map") {
        	if (!this.anchor) {
        		var link = angular.element("Adicionar ao grupo");
        		var divlist = angular.element(this.content).find("div");
        		var gmrev;
        		for(var i = 0; i < divlist.length; i++) {
        			if(angular.element(divlist[i]).hasClass("gm-rev")) {
        				gmrev = divlist[i];
        				break;
        			}
        		}
                angular.element(gmrev).append(angular.element("

").append(link));
        	}
        }
        set.apply(this, arguments);
 	}
}<|endoftext|>


<commit_before>'use strict';

import 'metal-soy-bundle';
import './requireWarning';

import core from 'metal';
import HTML2IncDom from 'html2incdom';
import IncrementalDomRenderer from 'metal-incremental-dom';
import SoyAop from './SoyAop';

// The injected data that will be passed to soy templates.
var ijData = {};

class Soy extends IncrementalDomRenderer {
	/**
	 * Adds the specified state keys to the component, if they don't exist yet.
	 * @param {Array<string>} keys
	 * @protected
	 */
	addMissingStateKeys_(keys) {
		if (this.addedMissingStateKeys_) {
			return;
		}

		this.addedMissingStateKeys_ = true;
		var component = this.component_;
		for (var i = 0; i < keys.length; i++) {
			if (!component.getStateKeyConfig(keys[i])) {
				component.addToState(keys[i], {}, component.getInitialConfig()[keys[i]]);
			}
		}
	}

	/**
	 * Copies the component's state to an object so it can be passed as it's
	 * template call's data. The copying needs to be done because, if the component
	 * itself is passed directly, some problems occur when soy tries to merge it
	 * with other data, due to property getters and setters. This is safer.
	 * @return {!Object}
	 * @protected
	 */
	buildTemplateData_() {
		var component = this.component_;
		var data = {};
		component.getStateKeys().forEach(key => {
			// Get all state values except "element", since it helps performance
			// and the element shouldn't be referenced inside a soy template anyway.
			if (key === 'element') {
				return;
			}

			var value = component[key];
			if (component.getStateKeyConfig(key).isHtml && core.isString(value)) {
				value = Soy.toIncDom(value);
			}
			data[key] = value;
		});
		return data;
	}

	/**
	 * Returns the requested template function. This function will be wrapped in
	 * another though, just to defer the requirement of the template's module
	 * being ready until the function is actually called.
	 * @param {string} namespace The soy template's namespace.
	 * @param {string} templateName The name of the template function.
	 * @return {!function()}
	 */
	static getTemplate(namespace, templateName) {
		return function(opt_data, opt_ignored, opt_ijData) {
			if (!goog.loadedModules_[namespace]) {
				throw new Error(
					'No template with namespace "' + namespace + '" has been loaded yet.'
				);
			}
			return goog.loadedModules_[namespace][templateName](opt_data, opt_ignored, opt_ijData);
		};
	}

	/**
	 * Handles an intercepted soy template call. If the call is for a component's
	 * main template, then it will be replaced with a call that incremental dom
	 * can use for both handling an instance of that component and rendering it.
	 * @param {!function()} originalFn The original template function that was
	 *     intercepted.
	 * @param {Object} data The data the template was called with.
	 * @protected
	 */
	static handleInterceptedCall_(originalFn, opt_data) {
		var ctor = originalFn.componentCtor;
		var data = opt_data;
		IncrementalDOM.elementVoid('Component', null, [], 'ctor', ctor, 'data', data);
	}

	/**
	 * Converts the given html string into an incremental dom function.
	 * @param {string} value
	 * @return {!function()}
	 */
	static toIncDom(value) {
		return HTML2IncDom.buildFn(value);
	}

	/**
	 * Overrides the original `IncrementalDomRenderer` method so that only
	 * state keys used by the main template can cause updates.
	 * @param {!Object} changes
	 * @return {boolean}
	 */
	shouldUpdate(changes) {
		var fn = this.component_.constructor.TEMPLATE;
		var params = fn ? SoyAop.getOriginalFn(fn).params : [];
		for (var i = 0; i < params.length; i++) {
			if (changes[params[i]]) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Registers the given templates to be used by `Soy` for the specified
	 * component constructor.
	 * @param {!Function} componentCtor The constructor of the component that
	 *     should use the given templates.
	 * @param {!Object} templates Object containing soy template functions.
	 * @param {string=} mainTemplate The name of the main template that should be
	 *     used to render the component. Defaults to "render".
	 */
	static register(componentCtor, templates, mainTemplate = 'render') {
		componentCtor.RENDERER = Soy;
		componentCtor.TEMPLATE = SoyAop.getOriginalFn(templates[mainTemplate]);
		componentCtor.TEMPLATE.componentCtor = componentCtor;
		SoyAop.registerForInterception(templates, mainTemplate);
	}

	/**
	 * Overrides the default method from `IncrementalDomRenderer` so the component's
	 * soy template can be used for rendering.
	 * @override
	 */
	renderIncDom() {
		var elementTemplate = this.component_.constructor.TEMPLATE;
		if (core.isFunction(elementTemplate)) {
			elementTemplate = SoyAop.getOriginalFn(elementTemplate);
			this.addMissingStateKeys_(elementTemplate.params);

			SoyAop.startInterception(Soy.handleInterceptedCall_);
			elementTemplate(this.buildTemplateData_(), null, ijData);
			SoyAop.stopInterception();
		} else {
			super.renderIncDom();
		}
	}

	/**
	 * Sets the injected data object that should be passed to templates.
	 * @param {Object} data
	 */
	static setInjectedData(data) {
		ijData = data || {};
	}
}

export default Soy;
export { Soy, SoyAop };
<commit_msg>Fixes alphabetical ordering of methods<commit_after>'use strict';

import 'metal-soy-bundle';
import './requireWarning';

import core from 'metal';
import HTML2IncDom from 'html2incdom';
import IncrementalDomRenderer from 'metal-incremental-dom';
import SoyAop from './SoyAop';

// The injected data that will be passed to soy templates.
var ijData = {};

class Soy extends IncrementalDomRenderer {
	/**
	 * Adds the specified state keys to the component, if they don't exist yet.
	 * @param {Array<string>} keys
	 * @protected
	 */
	addMissingStateKeys_(keys) {
		if (this.addedMissingStateKeys_) {
			return;
		}

		this.addedMissingStateKeys_ = true;
		var component = this.component_;
		for (var i = 0; i < keys.length; i++) {
			if (!component.getStateKeyConfig(keys[i])) {
				component.addToState(keys[i], {}, component.getInitialConfig()[keys[i]]);
			}
		}
	}

	/**
	 * Copies the component's state to an object so it can be passed as it's
	 * template call's data. The copying needs to be done because, if the component
	 * itself is passed directly, some problems occur when soy tries to merge it
	 * with other data, due to property getters and setters. This is safer.
	 * @return {!Object}
	 * @protected
	 */
	buildTemplateData_() {
		var component = this.component_;
		var data = {};
		component.getStateKeys().forEach(key => {
			// Get all state values except "element", since it helps performance
			// and the element shouldn't be referenced inside a soy template anyway.
			if (key === 'element') {
				return;
			}

			var value = component[key];
			if (component.getStateKeyConfig(key).isHtml && core.isString(value)) {
				value = Soy.toIncDom(value);
			}
			data[key] = value;
		});
		return data;
	}

	/**
	 * Returns the requested template function. This function will be wrapped in
	 * another though, just to defer the requirement of the template's module
	 * being ready until the function is actually called.
	 * @param {string} namespace The soy template's namespace.
	 * @param {string} templateName The name of the template function.
	 * @return {!function()}
	 */
	static getTemplate(namespace, templateName) {
		return function(opt_data, opt_ignored, opt_ijData) {
			if (!goog.loadedModules_[namespace]) {
				throw new Error(
					'No template with namespace "' + namespace + '" has been loaded yet.'
				);
			}
			return goog.loadedModules_[namespace][templateName](opt_data, opt_ignored, opt_ijData);
		};
	}

	/**
	 * Handles an intercepted soy template call. If the call is for a component's
	 * main template, then it will be replaced with a call that incremental dom
	 * can use for both handling an instance of that component and rendering it.
	 * @param {!function()} originalFn The original template function that was
	 *     intercepted.
	 * @param {Object} data The data the template was called with.
	 * @protected
	 */
	static handleInterceptedCall_(originalFn, opt_data) {
		var ctor = originalFn.componentCtor;
		var data = opt_data;
		IncrementalDOM.elementVoid('Component', null, [], 'ctor', ctor, 'data', data);
	}

	/**
	 * Registers the given templates to be used by `Soy` for the specified
	 * component constructor.
	 * @param {!Function} componentCtor The constructor of the component that
	 *     should use the given templates.
	 * @param {!Object} templates Object containing soy template functions.
	 * @param {string=} mainTemplate The name of the main template that should be
	 *     used to render the component. Defaults to "render".
	 */
	static register(componentCtor, templates, mainTemplate = 'render') {
		componentCtor.RENDERER = Soy;
		componentCtor.TEMPLATE = SoyAop.getOriginalFn(templates[mainTemplate]);
		componentCtor.TEMPLATE.componentCtor = componentCtor;
		SoyAop.registerForInterception(templates, mainTemplate);
	}

	/**
	 * Overrides the default method from `IncrementalDomRenderer` so the component's
	 * soy template can be used for rendering.
	 * @override
	 */
	renderIncDom() {
		var elementTemplate = this.component_.constructor.TEMPLATE;
		if (core.isFunction(elementTemplate)) {
			elementTemplate = SoyAop.getOriginalFn(elementTemplate);
			this.addMissingStateKeys_(elementTemplate.params);

			SoyAop.startInterception(Soy.handleInterceptedCall_);
			elementTemplate(this.buildTemplateData_(), null, ijData);
			SoyAop.stopInterception();
		} else {
			super.renderIncDom();
		}
	}

	/**
	 * Sets the injected data object that should be passed to templates.
	 * @param {Object} data
	 */
	static setInjectedData(data) {
		ijData = data || {};
	}

	/**
	 * Overrides the original `IncrementalDomRenderer` method so that only
	 * state keys used by the main template can cause updates.
	 * @param {!Object} changes
	 * @return {boolean}
	 */
	shouldUpdate(changes) {
		var fn = this.component_.constructor.TEMPLATE;
		var params = fn ? SoyAop.getOriginalFn(fn).params : [];
		for (var i = 0; i < params.length; i++) {
			if (changes[params[i]]) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Converts the given html string into an incremental dom function.
	 * @param {string} value
	 * @return {!function()}
	 */
	static toIncDom(value) {
		return HTML2IncDom.buildFn(value);
	}
}

export default Soy;
export { Soy, SoyAop };
<|endoftext|>