<commit_before>/*
 * jQuery LiveTwitter 1.7.1
 * - Live updating Twitter plugin for jQuery
 *
 * Copyright (c) 2009-2011 Inge Jørgensen (elektronaut.no)
 * Licensed under the MIT license (MIT-LICENSE.txt)
 *
 * $Date: 2011/07/12$
 */

/*jslint browser: true, devel: true, onevar: false, immed: false, regexp: false, indent: 2 */
/*global window: false, jQuery: false */

/*
 * Usage example:
 * $("#twitterSearch").liveTwitter('bacon', {limit: 10, rate: 15000});
 */

(function ($) {

  // Extend jQuery with a reverse function if it isn't already defined
  if (!$.fn.reverse) {
    $.fn.reverse = function () {
      return this.pushStack(this.get().reverse(), arguments);
    };
  }

  $.fn.liveTwitter = function (query, options, callback) {
    var domNode = this;
    $(this).each(function () {
      var settings = {};

      // Does this.twitter already exist? Let's just change the settings.
      if (this.twitter) {
        settings = $.extend(this.twitter.settings, options);
        this.twitter.settings = settings;
        if (query) {
          this.twitter.query = query;
        }
        if (this.twitter.interval) {
          this.twitter.refresh();
        }
        if (callback) {
          this.twitter.callback = callback;
        }

      // ..if not, let's initialize.
      } else {

        // These are the default settings.
        settings = $.extend({
          mode:      'search', // Mode, valid options are: 'search', 'user_timeline', 'list', 'home_timeline'
          rate:      15000,    // Refresh rate in ms
          limit:     10,       // Limit number of results
          imageSize: 24,       // Size of image in pixels
          refresh:   true,
          timeLinks: true,
          retweets:  false,
          service:   false,
          localization: {
            seconds: 'seconds ago',
            minute:  'a minute ago',
            minutes: 'minutes ago',
            hour:    'an hour ago',
            hours:   'hours ago',
            day:     'a day ago',
            days:    'days ago'
          }
        }, options);
        // showAuthor should default to true unless mode is 'user_timeline'.
        if (typeof settings.showAuthor === "undefined") {
          settings.showAuthor = (settings.mode === 'user_timeline') ? false : true;
        }

        // Set up a dummy function for the Twitter API callback.
        if (!window.twitter_callback) {
          window.twitter_callback = function () {
            return true;
          };
        }

        this.twitter = {
          settings:      settings,
          query:         query,
          interval:      false,
          container:     this,
          lastTimeStamp: 0,
          callback:      callback,

          // Convert the time stamp to a more human readable format
          relativeTime: function (timeString) {
            var parsedDate = Date.parse(timeString);
            var delta = (Date.parse(Date()) - parsedDate) / 1000;
            var r = '';
            if  (delta < 60) {
              r = delta + " " + settings.localization.seconds;
            } else if (delta < 120) {
              r = settings.localization.minute;
            } else if (delta < (45 * 60)) {
              r = (parseInt(delta / 60, 10)).toString() + " " + settings.localization.minutes;
            } else if (delta < (90 * 60)) {
              r = settings.localization.hour;
            } else if (delta < (24 * 60 * 60)) {
              r = '' + (parseInt(delta / 3600, 10)).toString() + " " + settings.localization.hours;
            } else if (delta < (48 * 60 * 60)) {
              r = settings.localization.day;
            } else {
              r = (parseInt(delta / 86400, 10)).toString() + " " + settings.localization.days;
            }
            return r;
          },

          // Update the relative timestamps
          updateTimestamps: function () {
            var twitter = this;
            $(twitter.container).find('span.time').each(function () {
              var time_element = twitter.settings.timeLinks ? $(this).find('a') : $(this);
              time_element.html(twitter.relativeTime(this.timeStamp));
            });
          },

          apiURL: function () {
            var params = {};

            var protocol = (window.location.protocol === 'https:') ? 'https:' : 'http:';
            var baseURL  = 'api.twitter.com/1/';
            var endpoint = '';

            // Override for Twitter-compatible APIs like Status.net
            if (this.settings.service) {
              baseURL = this.settings.service + '/api/';
            }

            // Search mode
            if (this.settings.mode === 'search') {
              baseURL  = (this.settings.service) ? this.settings.service + '/api/' : 'search.twitter.com/';
              endpoint = 'search';
              params   = {
                q:        (this.query && this.query !== '') ? this.query : null,
                geocode:  this.settings.geocode,
                lang:     this.settings.lang,
                rpp:      (this.settings.rpp) ? this.settings.rpp : this.settings.limit
              };

            // User/home timeline mode
            } else if (this.settings.mode === 'user_timeline' || this.settings.mode === 'home_timeline') {
              endpoint = 'statuses/' + this.settings.mode + '/' + encodeURIComponent(this.query);
              params = {
                count:       this.settings.limit,
                include_rts: (this.settings.mode === 'user_timeline' && this.settings.retweets) ? '1' : null
              };

            // List mode
            } else if (this.settings.mode === 'list') {
              endpoint = encodeURIComponent(this.query.user) +
                         '/lists/' +
                         encodeURIComponent(this.query.list) +
                         '/statuses';
              params = {
                per_page: this.settings.limit
              };
            }

            // Construct the query string
            var queryString = [];
            for (var param in params) {
              if (params.hasOwnProperty(param) && typeof params[param] !== 'undefined' && params[param] !== null) {
                queryString[queryString.length] = param + '=' + encodeURIComponent(params[param]);
              }
            }
            queryString = queryString.join("&");

            // Return the full URL
            return protocol + '//' + baseURL + endpoint + '.json?' + queryString + '&callback=?';
          },

          // The different APIs will format the results slightly different,
          // so this method normalizes the tweet object.
          parseTweet: function (json) {
            var tweet = {
              id:         (json.id_str) ? json.id_str : json.id,
              text:       json.text,
              created_at: json.created_at
            };

            // Search/regular API differences
            if (this.settings.mode === 'search') {
              tweet = $.extend(tweet, {
                screen_name:       json.from_user,
                profile_image_url: json.profile_image_url
              });
            } else {
              tweet = $.extend(tweet, {
                screen_name:       json.user.screen_name,
                profile_image_url: json.user.profile_image_url,
                created_at:        json.created_at.replace(/^(\w+)\s(\w+)\s(\d+)(.*)(\s\d+)$/, "$1, $3 $2$5$4") // Fix for IE
              });
            }

            // Twitter/Status.net
            if (this.settings.service) {
              tweet = $.extend(tweet, {
                url:         'http://' + this.settings.service + '/notice/' + tweet.id,
                profile_url: json.user.statusnet_profile_url
              });
              if (window.location.protocol === 'https:') {
                tweet.profile_image_url = tweet.profile_image_url.replace('http:', 'https:');
              }

            } else {
              tweet = $.extend(tweet, {
                url:         'http://twitter.com/#!/' + tweet.screen_name + '/status/' + tweet.id,
                profile_url: 'http://twitter.com/#!/' + tweet.screen_name
              });
              // Someday, Twitter will add HTTPS support to twimg.com, but until then
              // we have to rewrite the profile image urls to the old Amazon S3 urls.
              if (window.location.protocol === 'https:') {
                var matches = tweet.profile_image_url.match(/http[s]?:\/\/a[0-9]\.twimg\.com\/(\w+)\/(\w+)\/(.*?)\.(\w+)/i);
                if (matches) {
                  tweet.profile_image_url = "https://s3.amazonaws.com/twitter_production/" + matches[1] + "/" + matches[2] + "/" + matches[3] + "." + matches[4];
                } else {
                  // Failsafe, if profile image url does not match the pattern above
                  // then, at least, change the protocol to HTTPS.
                  // The image may not load, but at least the page stays secure.
                  tweet.profile_image_url = tweet.profile_image_url.replace('http:', 'https:');
                }
              }
            }

            return tweet;
          },

          // Parses the tweet body, linking URLs, #hashtags and @usernames.
          parseText: function (text) {
            // URLs
            text = text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/, function (m) {
              return '' + m + '';
            });

            // Twitter
            if (!this.settings.service) {
              // @usernames
              text = text.replace(/@[A-Za-z0-9_]+/g, function (u) {
                return '' + u + '';
              });
              // #hashtags
              text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) {
                return '' + u + '';
              });

            // Other APIs
            } else {
              text = text.replace(/@[A-Za-z0-9_]+/g, function (u) {
                return '' + u + '';
              });
              text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) {
                return '' + u + '';
              });
            }

            return text;
          },

          // Renders a tweet to HTML
          renderTweet: function (tweet) {
            var html = '
';

            if (this.settings.showAuthor) {
              html += '';
              html += '
' + tweet.screen_name + ': ';
            } else {
              html += '
 ';
            }

            html += this.parseText(tweet.text);

            if (this.settings.timeLinks) {
              html += ' ';
              html += '';
              html += this.relativeTime(tweet.created_at);
              html += '';
            } else {
              html += ' ' + this.relativeTime(tweet.created_at) + '';
            }

            html += '

';

            return html;
          },

          // Handle reloading
          refresh: function (initialize) {
            var twitter = this;
            if (twitter.settings.refresh || initialize) {

              $.getJSON(twitter.apiURL(), function (json) {
                var newTweets = 0;

                // The search and regular APIs differ somewhat
                var results = (twitter.settings.mode === 'search') ? json.results : json;

                $(results).reverse().each(function () {
                  var tweet = twitter.parseTweet(this);

                  // Check if tweets should be filtered
                  if (!twitter.settings.filter || twitter.settings.filter(this)) {
                    // Check if this is actually a new tweet
                    if (Date.parse(tweet.created_at) > twitter.lastTimeStamp) {

                      // Insert the HTML
                      $(twitter.container).prepend(twitter.renderTweet(tweet));

                      // Make a note of the timestamp on the first span
                      // so we can update it later.
                      $(twitter.container).find('span.time:first').each(function () {
                        this.timeStamp = tweet.created_at;
                      });

                      // Fade in new tweets unless this is the first load.
                      if (!initialize) {
                        $(twitter.container).find('.tweet-' + tweet.id).hide().fadeIn();
                      }

                      // Remember the last timestamp for the next refresh.
                      twitter.lastTimeStamp = Date.parse(tweet.created_at);

                      newTweets += 1;
                    }
                  }
                });

                // Did we get any new tweets?
                if (newTweets > 0) {
                  // Remove old entries exceeding the limit
                  $(twitter.container).find('div.tweet:gt(' + (twitter.settings.limit - 1) + ')').remove();

                  // Run callback
                  if (twitter.callback) {
                    twitter.callback(domNode, newTweets);
                  }

                  // Trigger event
                  $(domNode).trigger('tweets');
                }
              });
            }
          },

          // Start refreshing
          start: function () {
            var twitter = this;
            if (!this.interval) {
              this.interval = setInterval(function () {
                twitter.refresh();
              }, twitter.settings.rate);
              this.refresh(true);
            }
          },

          // Stop refreshing
          stop: function () {
            if (this.interval) {
              clearInterval(this.interval);
              this.interval = false;
            }
          },

          // Clear all tweets
          clear: function () {
            $(this.container).find('div.tweet').remove();
            this.lastTimeStamp = null;
          }
        };

        var twitter = this.twitter;

        // Update the timestamps in realtime
        this.timeInterval = setInterval(function () {
          twitter.updateTimestamps();
        }, 5000);

        this.twitter.start();
      }
    });
    return this;
  };
})(jQuery);
<commit_msg>Support for favorites mode<commit_after>/*
 * jQuery LiveTwitter 1.7.1
 * - Live updating Twitter plugin for jQuery
 *
 * Copyright (c) 2009-2011 Inge Jørgensen (elektronaut.no)
 * Licensed under the MIT license (MIT-LICENSE.txt)
 *
 * $Date: 2011/07/12$
 */

/*jslint browser: true, devel: true, onevar: false, immed: false, regexp: false, indent: 2 */
/*global window: false, jQuery: false */

/*
 * Usage example:
 * $("#twitterSearch").liveTwitter('bacon', {limit: 10, rate: 15000});
 */

(function ($) {

  // Extend jQuery with a reverse function if it isn't already defined
  if (!$.fn.reverse) {
    $.fn.reverse = function () {
      return this.pushStack(this.get().reverse(), arguments);
    };
  }

  $.fn.liveTwitter = function (query, options, callback) {
    var domNode = this;
    $(this).each(function () {
      var settings = {};

      // Does this.twitter already exist? Let's just change the settings.
      if (this.twitter) {
        settings = $.extend(this.twitter.settings, options);
        this.twitter.settings = settings;
        if (query) {
          this.twitter.query = query;
        }
        if (this.twitter.interval) {
          this.twitter.refresh();
        }
        if (callback) {
          this.twitter.callback = callback;
        }

      // ..if not, let's initialize.
      } else {

        // These are the default settings.
        settings = $.extend({
          mode:      'search', // Mode, valid options are: 'search', 'user_timeline', 'list', 'home_timeline'
          rate:      15000,    // Refresh rate in ms
          limit:     10,       // Limit number of results
          imageSize: 24,       // Size of image in pixels
          refresh:   true,
          timeLinks: true,
          retweets:  false,
          service:   false,
          localization: {
            seconds: 'seconds ago',
            minute:  'a minute ago',
            minutes: 'minutes ago',
            hour:    'an hour ago',
            hours:   'hours ago',
            day:     'a day ago',
            days:    'days ago'
          }
        }, options);
        // showAuthor should default to true unless mode is 'user_timeline'.
        if (typeof settings.showAuthor === "undefined") {
          settings.showAuthor = (settings.mode === 'user_timeline') ? false : true;
        }

        // Set up a dummy function for the Twitter API callback.
        if (!window.twitter_callback) {
          window.twitter_callback = function () {
            return true;
          };
        }

        this.twitter = {
          settings:      settings,
          query:         query,
          interval:      false,
          container:     this,
          lastTimeStamp: 0,
          callback:      callback,

          // Convert the time stamp to a more human readable format
          relativeTime: function (timeString) {
            var parsedDate = Date.parse(timeString);
            var delta = (Date.parse(Date()) - parsedDate) / 1000;
            var r = '';
            if  (delta < 60) {
              r = delta + " " + settings.localization.seconds;
            } else if (delta < 120) {
              r = settings.localization.minute;
            } else if (delta < (45 * 60)) {
              r = (parseInt(delta / 60, 10)).toString() + " " + settings.localization.minutes;
            } else if (delta < (90 * 60)) {
              r = settings.localization.hour;
            } else if (delta < (24 * 60 * 60)) {
              r = '' + (parseInt(delta / 3600, 10)).toString() + " " + settings.localization.hours;
            } else if (delta < (48 * 60 * 60)) {
              r = settings.localization.day;
            } else {
              r = (parseInt(delta / 86400, 10)).toString() + " " + settings.localization.days;
            }
            return r;
          },

          // Update the relative timestamps
          updateTimestamps: function () {
            var twitter = this;
            $(twitter.container).find('span.time').each(function () {
              var time_element = twitter.settings.timeLinks ? $(this).find('a') : $(this);
              time_element.html(twitter.relativeTime(this.timeStamp));
            });
          },

          apiURL: function () {
            var params = {};

            var protocol = (window.location.protocol === 'https:') ? 'https:' : 'http:';
            var baseURL  = 'api.twitter.com/1/';
            var endpoint = '';

            // Override for Twitter-compatible APIs like Status.net
            if (this.settings.service) {
              baseURL = this.settings.service + '/api/';
            }

            // Search mode
            if (this.settings.mode === 'search') {
              baseURL  = (this.settings.service) ? this.settings.service + '/api/' : 'search.twitter.com/';
              endpoint = 'search';
              params   = {
                q:        (this.query && this.query !== '') ? this.query : null,
                geocode:  this.settings.geocode,
                lang:     this.settings.lang,
                rpp:      (this.settings.rpp) ? this.settings.rpp : this.settings.limit
              };

            // User/home timeline mode
            } else if (this.settings.mode === 'user_timeline' || this.settings.mode === 'home_timeline') {
              endpoint = 'statuses/' + this.settings.mode + '/' + encodeURIComponent(this.query);
              params = {
                count:       this.settings.limit,
                include_rts: (this.settings.mode === 'user_timeline' && this.settings.retweets) ? '1' : null
              };

            // Favorites mode
            } else if (this.settings.mode === 'favorites') {
              endpoint = 'favorites';
              params = {
                id:       encodeURIComponent(this.query)
              };

            // List mode
            } else if (this.settings.mode === 'list') {
              endpoint = encodeURIComponent(this.query.user) +
                         '/lists/' +
                         encodeURIComponent(this.query.list) +
                         '/statuses';
              params = {
                per_page: this.settings.limit
              };
            }

            // Construct the query string
            var queryString = [];
            for (var param in params) {
              if (params.hasOwnProperty(param) && typeof params[param] !== 'undefined' && params[param] !== null) {
                queryString[queryString.length] = param + '=' + encodeURIComponent(params[param]);
              }
            }
            queryString = queryString.join("&");

            // Return the full URL
            return protocol + '//' + baseURL + endpoint + '.json?' + queryString + '&callback=?';
          },

          // The different APIs will format the results slightly different,
          // so this method normalizes the tweet object.
          parseTweet: function (json) {
            var tweet = {
              id:         (json.id_str) ? json.id_str : json.id,
              text:       json.text,
              created_at: json.created_at
            };

            // Search/regular API differences
            if (this.settings.mode === 'search') {
              tweet = $.extend(tweet, {
                screen_name:       json.from_user,
                profile_image_url: json.profile_image_url
              });
            } else {
              tweet = $.extend(tweet, {
                screen_name:       json.user.screen_name,
                profile_image_url: json.user.profile_image_url,
                created_at:        json.created_at.replace(/^(\w+)\s(\w+)\s(\d+)(.*)(\s\d+)$/, "$1, $3 $2$5$4") // Fix for IE
              });
            }

            // Twitter/Status.net
            if (this.settings.service) {
              tweet = $.extend(tweet, {
                url:         'http://' + this.settings.service + '/notice/' + tweet.id,
                profile_url: json.user.statusnet_profile_url
              });
              if (window.location.protocol === 'https:') {
                tweet.profile_image_url = tweet.profile_image_url.replace('http:', 'https:');
              }

            } else {
              tweet = $.extend(tweet, {
                url:         'http://twitter.com/#!/' + tweet.screen_name + '/status/' + tweet.id,
                profile_url: 'http://twitter.com/#!/' + tweet.screen_name
              });
              // Someday, Twitter will add HTTPS support to twimg.com, but until then
              // we have to rewrite the profile image urls to the old Amazon S3 urls.
              if (window.location.protocol === 'https:') {
                var matches = tweet.profile_image_url.match(/http[s]?:\/\/a[0-9]\.twimg\.com\/(\w+)\/(\w+)\/(.*?)\.(\w+)/i);
                if (matches) {
                  tweet.profile_image_url = "https://s3.amazonaws.com/twitter_production/" + matches[1] + "/" + matches[2] + "/" + matches[3] + "." + matches[4];
                } else {
                  // Failsafe, if profile image url does not match the pattern above
                  // then, at least, change the protocol to HTTPS.
                  // The image may not load, but at least the page stays secure.
                  tweet.profile_image_url = tweet.profile_image_url.replace('http:', 'https:');
                }
              }
            }

            return tweet;
          },

          // Parses the tweet body, linking URLs, #hashtags and @usernames.
          parseText: function (text) {
            // URLs
            text = text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/, function (m) {
              return '' + m + '';
            });

            // Twitter
            if (!this.settings.service) {
              // @usernames
              text = text.replace(/@[A-Za-z0-9_]+/g, function (u) {
                return '' + u + '';
              });
              // #hashtags
              text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) {
                return '' + u + '';
              });

            // Other APIs
            } else {
              text = text.replace(/@[A-Za-z0-9_]+/g, function (u) {
                return '' + u + '';
              });
              text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) {
                return '' + u + '';
              });
            }

            return text;
          },

          // Renders a tweet to HTML
          renderTweet: function (tweet) {
            var html = '
';

            if (this.settings.showAuthor) {
              html += '';
              html += '
' + tweet.screen_name + ': ';
            } else {
              html += '
 ';
            }

            html += this.parseText(tweet.text);

            if (this.settings.timeLinks) {
              html += ' ';
              html += '';
              html += this.relativeTime(tweet.created_at);
              html += '';
            } else {
              html += ' ' + this.relativeTime(tweet.created_at) + '';
            }

            html += '

';

            return html;
          },

          // Handle reloading
          refresh: function (initialize) {
            var twitter = this;
            if (twitter.settings.refresh || initialize) {

              $.getJSON(twitter.apiURL(), function (json) {
                var newTweets = 0;

                // The search and regular APIs differ somewhat
                var results = (twitter.settings.mode === 'search') ? json.results : json;

                $(results).reverse().each(function () {
                  var tweet = twitter.parseTweet(this);

                  // Check if tweets should be filtered
                  if (!twitter.settings.filter || twitter.settings.filter(this)) {
                    // Check if this is actually a new tweet
                    if (Date.parse(tweet.created_at) > twitter.lastTimeStamp) {

                      // Insert the HTML
                      $(twitter.container).prepend(twitter.renderTweet(tweet));

                      // Make a note of the timestamp on the first span
                      // so we can update it later.
                      $(twitter.container).find('span.time:first').each(function () {
                        this.timeStamp = tweet.created_at;
                      });

                      // Fade in new tweets unless this is the first load.
                      if (!initialize) {
                        $(twitter.container).find('.tweet-' + tweet.id).hide().fadeIn();
                      }

                      // Remember the last timestamp for the next refresh.
                      twitter.lastTimeStamp = Date.parse(tweet.created_at);

                      newTweets += 1;
                    }
                  }
                });

                // Did we get any new tweets?
                if (newTweets > 0) {
                  // Remove old entries exceeding the limit
                  $(twitter.container).find('div.tweet:gt(' + (twitter.settings.limit - 1) + ')').remove();

                  // Run callback
                  if (twitter.callback) {
                    twitter.callback(domNode, newTweets);
                  }

                  // Trigger event
                  $(domNode).trigger('tweets');
                }
              });
            }
          },

          // Start refreshing
          start: function () {
            var twitter = this;
            if (!this.interval) {
              this.interval = setInterval(function () {
                twitter.refresh();
              }, twitter.settings.rate);
              this.refresh(true);
            }
          },

          // Stop refreshing
          stop: function () {
            if (this.interval) {
              clearInterval(this.interval);
              this.interval = false;
            }
          },

          // Clear all tweets
          clear: function () {
            $(this.container).find('div.tweet').remove();
            this.lastTimeStamp = null;
          }
        };

        var twitter = this.twitter;

        // Update the timestamps in realtime
        this.timeInterval = setInterval(function () {
          twitter.updateTimestamps();
        }, 5000);

        this.twitter.start();
      }
    });
    return this;
  };
})(jQuery);
<|endoftext|>


<commit_before>/*
	Mailbox 3 - Replacing mailto-links with contact-forms.
	Copyright by Leonard Lamprecht - //github.com/leo/mailbox
	License: http://git.io/c9_RLA
*/

(function(e){e.fn.mailbox=function(t){function i(t,n,i,s,o,u){if(typeof o!=="undefined"&&o!==false){e(r+' input[name="subject"]').val(o)}if(typeof u!=="undefined"&&u!==false){e(r+" textarea").val(u)}if(typeof i!=="undefined"&&i!==false){var i=false}if(typeof s!=="undefined"&&s!==false){var s=false}e(r).attr("class","on_ma_way");e(r+' input[name="sess_id"]').attr("value",t);e(r).animate({right:"0"},300,function(){e(r).attr("class","open")})}function s(){e(r).attr("class","on_ma_way");var t=e(r).css("width");var i="-"+t;e(r).animate({right:i},300);e(r+" .success").fadeOut(200);setTimeout(function(){e(r+" form").fadeIn(200)},250);setTimeout(function(){e(r+' label[for="name"]').html(n.label.name);e(r+' label[for="email"]').html(n.label.email);e(r+' label[for="subject"]').html(n.label.subject);e(r+' label[for="message"]').html(n.label.message);e(r+' input[type="text"], '+r+" textarea").removeAttr("disabled");e(r+" .send").html(n.send_text);e(r+' input[name="name"]').val("");e(r+' input[name="email"]').val("");e(r+' input[name="subject"]').val("");e(r+" textarea").val("");e(r).attr("class","closed");e(r+' input[type="hidden"]').removeAttr("value");e(r+" label.warning").removeClass("warning")},500)}var n={send_text:"Send message",success_text:"Your message has been sent successfully!",label:{name:"name",email:"e-mail",subject:"subject",message:"message"},send_url:"mb_send.php",complete:null};if(t){e.extend(n,t)}var r="#mailbox";e(this).each(function(){var t="";var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";for(var r=0;r<10;r++)t+=n.charAt(Math.floor(Math.random()*n.length));e(this).attr("data-mail-id",t);var i=e(this).attr("href");if(i.indexOf(",")>-1){if(i.indexOf(", ")>-1){}else{console.warn('The e-mail addresses in the mailto-links should be seperatet with a comma + space (e.g. "hi@test.net, ho@test.org").')}}if(i.indexOf("@")>6){if(i.indexOf("?")>6){var s=i.substr(0,i.indexOf("?"));var o=s.split("mailto:")[1]}else{var o=i.split("mailto:")[1]}if(i.indexOf("?")>6){var u=i.split("?")[1];var a=u.split("&")[0];if(typeof a==="undefined"){var a="-"}var f=u.split("&")[1];if(typeof f==="undefined"){var f="-"}var l=u.split("&")[2];if(typeof l==="undefined"){var l="-"}var c=u.split("&")[3];if(typeof c==="undefined"){var c="-"}if(a.indexOf("body")==0){var h=a.split("body=")[1]}if(a.indexOf("subject")==0){var p=a.split("subject=")[1]}if(a.indexOf("cc")==0){var d=a.split("cc=")[1]}if(a.indexOf("bcc")==0){var v=a.split("bcc=")[1]}if(f.indexOf("body")==0){var h=f.split("body=")[1]}if(f.indexOf("subject")==0){var p=f.split("subject=")[1]}if(f.indexOf("cc")==0){var d=f.split("cc=")[1]}if(f.indexOf("bcc")==0){var v=f.split("bcc=")[1]}if(l.indexOf("body")==0){var h=l.split("body=")[1]}if(l.indexOf("subject")==0){var p=l.split("subject=")[1]}if(l.indexOf("cc")==0){var d=l.split("cc=")[1]}if(l.indexOf("bcc")==0){var v=l.split("bcc=")[1]}if(c.indexOf("body")==0){var h=c.split("body=")[1]}if(c.indexOf("subject")==0){var p=c.split("subject=")[1]}if(c.indexOf("cc")==0){var d=c.split("cc=")[1]}if(c.indexOf("bcc")==0){var v=c.split("bcc=")[1]}}e(this).attr("data-msg-receiver",o);if(typeof p==="undefined"){}else{e(this).attr("data-msg-subject",p)}if(typeof h==="undefined"){}else{e(this).attr("data-msg-body",h)}if(typeof d==="undefined"){}else{e(this).attr("data-msg-cc",d)}if(typeof v==="undefined"){}else{e(this).attr("data-msg-bcc",v)}e(this).attr("href","#")}else{console.error("The href-attribute of one element does not contain a valid mailto-link.")}});e(this).click(function(t){t.preventDefault();var n=e(this).attr("data-mail-id");var o=e(this).attr("data-msg-receiver");var u=e(this).attr("data-msg-subject");var a=e(this).attr("data-msg-body");var f=e(this).attr("data-msg-cc");var l=e(this).attr("data-msg-bcc");if(e(r).hasClass("closed")){i(n,o,f,l,u,a)}if(e(r).hasClass("open")){s();var c=e(r+' input[name="sess_id"]').val();if(n!=c){setTimeout(function(){i(n,o,f,l,u,a)},1e3)}}});e(document).ready(function(){e(r+" .close").click(function(){s()})});var o='
'+'x'+'<form method="post" action="'+n.send_url+'">'+'<input type="hidden" name="sess_id">'+''+n.label.name+""+'<input name="name" id="name" type="text" autocomplete="off" >'+''+n.label.email+""+'<input name="email" id="email" type="text" autocomplete="off">'+''+n.label.subject+""+'<input name="subject" id="subject" type="text" autocomplete="off">'+''+n.label.message+""+'<textarea name="message" id="message" rows="6" ></textarea>'+'<button class="send" name="submit" type="submit">'+n.send_text+"</button>"+"</form>"+'
'+"✓"+"
"+n.success_text+"
"+"

";e("body").append(o);e("#mailbox form").submit(function(){function u(e){var t=/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;return t.test(e)}var t=e(r+' input[name="name"]').val();var i=e(r+' input[name="email"]').val();var s=e(r+' input[name="subject"]').val();var o=e(r+" textarea").val();if(!t||!i||!u(i)||!s||!o){if(!t){e(r+' label[for="name"]').addClass("warning");e(r+' label[for="name"]').html(n.label.name+" [!]")}else{e(r+' label[for="name"]').removeClass("warning");e(r+' label[for="name"]').html(n.label.name)}if(!i||!u(i)){e(r+' label[for="email"]').addClass("warning");e(r+' label[for="email"]').html(n.label.email+" [!]")}else{e(r+' label[for="email"]').removeClass("warning");e(r+' label[for="email"]').html(n.label.email)}if(!s){e(r+' label[for="subject"]').addClass("warning");e(r+' label[for="subject"]').html(n.label.subject+" [!]")}else{e(r+' label[for="subject"]').removeClass("warning");e(r+' label[for="subject"]').html(n.label.subject)}if(!o){e(r+' label[for="message"]').addClass("warning");e(r+' label[for="message"]').html(n.label.message+" [!]")}else{e(r+' label[for="message"]').removeClass("warning");e(r+' label[for="message"]').html(n.label.message)}}else{var a=e(r+' input[name="sess_id"]').val();var f=e('a[data-mail-id="'+a+'"]').attr("data-msg-receiver");var l=e('a[data-mail-id="'+a+'"]').attr("data-msg-cc");var c=e('a[data-mail-id="'+a+'"]').attr("data-msg-bcc");if(typeof l==="undefined"||l==false){var l=false}if(typeof c==="undefined"||c==false){var c=false}var h=e("#mailbox form").attr("action");e.ajax({type:"POST",url:h,data:{to:f,cc:l,bcc:c,name:e(r+' input[name="name"]').val(),address:e(r+' input[name="email"]').val(),subject:e(r+' input[name="subject"]').val(),message:e(r+" textarea").val()},success:function(t){if(t=="success"){e(r+' input[type="hidden"]').removeAttr("value");e(r+" form").fadeOut(200);setTimeout(function(){e(r+" .success").fadeIn(200);setTimeout(function(){if(e.isFunction(n.complete)){n.complete.call(this)}},200)},250)}}})}return false})}})(jQuery)
<commit_msg>Version 4<commit_after>/*
	Mailbox 4 - A contact-form for the new web.
	Made by Leonard Lamprecht - //github.com/leo/mailbox
*/

(function(e){e.fn.mailbox=function(t){function i(t){if(e(r).hasClass(t)){return true}return false}function s(t){e.each(t,function(t,n){if(n!=null){e(r).find('[name="'+t+'"]').val(n);e(r).data(t,n)}});e(r).attr("class","open")}function o(t,i){e(r).attr("class","closed");setTimeout(function(){e.each(attributes,function(t,n){e(r).removeData(t)});e(r).find(".success").fadeOut(200,function(){e.each(n.labels,function(t,i){e(r).find('label[for="'+t+'"]').html(n.labels[i]);e(r).find('input[type="text"], textarea').removeAttr("disabled").val("");e(r).find(".warning").removeClass("warning")});e(r).find("form").fadeIn(200,function(){if(t=="reopen"&&e.isFunction(i)){i()}})})},300)}function u(){var t='
x';t+='<form method="post" action="'+n.script+'">';e.each(n.labels,function(e,n){t+=''+n+"";if(e!=="body"){t+='<input name="'+e+'" id="'+e+'" type="text" autocomplete="off" />'}else{t+='<textarea name="body" id="'+e+'" rows="6" ></textarea>'}});t+='<button class="send" name="submit" type="submit">'+n.send+"</button>";t+='</form>
✓
'+n.success+"

";return t}var n={send:"Send message",success:"Your message has been sent successfully!",labels:{name:"name",email:"e-mail",subject:"subject",body:"message"},script:"mailbox.php",complete:null};if(t){e.extend(n,t)}var r="#mailbox";e("body").append(u());e(r).find(".close").click(function(){o()});e(this).click(function(t){var n=e(this).attr("href");attributes={to:null,subject:null,body:null,cc:null,bcc:null};if(n.indexOf("?")>-1){var u=n.split("?")[1];if(u.indexOf("&")>-1){var a=u.split("&");e.each(a,function(e,t){var n=t.split("=");attributes[n[0]]=n[1]})}else{var f=u.split("=");attributes[f[0]]=f[1]}attributes["to"]=n.split("mailto:")[1].split("?")[0]}else{attributes["to"]=n.split("mailto:")[1]}var l="mailbox-on";if(i("closed")){s(attributes)}else if(i("open")){if(!e(this).hasClass(l)){e.each(attributes,function(t,n){if(n!==null&&n!==e(r).data(t)){o("reopen",function(){s(attributes)});return false}})}else{o()}}e("."+l).removeClass(l);e(this).addClass(l);t.preventDefault()});var a=e(r).find("form");e(a).submit(function(){function t(e){var t=/^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;return t.test(e)}e.each(n.labels,function(t){attributes[t]=e(a).find('[name="'+t+'"]').val()});e.each(attributes,function(n,r){function s(){e(i).removeClass("warning")}function o(){e(i).addClass("warning")}var i=e(a).find('[for="'+n+'"]');if(!r){o()}else{if(n=="email"){if(t(r)){s()}else{o()}}else{s()}}});if(e(a).find(".warning").length==0){attributes["adress"]=e(a).find('[name="email"]').val();e.ajax({type:"POST",url:e(a).attr("action"),data:attributes,success:function(t){if(t==1){e(a).fadeOut(200,function(){e(r).find(".success").fadeIn(200,function(){if(e.isFunction(n.complete)){n.complete()}})})}}})}return false})}})(jQuery)
<|endoftext|>


<commit_before>(function($, undefined) {
  'use strict';

  var Plugin, defaults, namespace;

  // Default options and settings
  namespace ='simplemodal';
  defaults = {
    top: null,
    overlay: 0.5,
    closeButton: null,
    duration: 200,
    onOpen: function() {},
    onClose: function() {}
  };

  // PLUGIN --------------------------------------------------------------------
  //
  // Declare the plugin fuction, isolated in a sub-module pattern to prevent
  // leaks and preserve vars scope.
  //
  Plugin = (function() {

    // The Plugin object
    function Plugin(element, options) {
      // Store elements to access it later easily
      this.element = element;
      this.options = $.extend({}, defaults, options);
      this._defaults = defaults;
      this._name = namespace;

      // Fire the init method
      this.init();
    }

    Plugin.prototype.init = function() {
      this.$overlay = setOverlay.call(this);
      this.$el = setModal.call(this, el);
    };

    var setOverlay = function () {
      return $('<div/>', {'class': 'sm-overlay'})
      .css({
        position: 'fixed',
        zIndex: 100,
        top: 0,
        left: 0,
        height: '100%',
        width: '100%',
        background: '#000'
      })
      .hide()
      .appendTo('body');
    };

    var setModal = function (el) {
      var $el = $('<div/>', {'class': 'sm-modal'})
      .css({
        position : 'fixed',
        zIndex: 101,
        top: this.options.top? this.options.top : '50%',
        left : '50%'
      })
      .hide()
      .appendTo('body');

      $('<div/>', {'class': 'sm-content'})
      .append($(el))
      .appendTo($el);

      if (this.options.closeButton) {
        $('<button/>', {'class': 'sm-close'})
        .on('click', $.proxy(this.close, this))
        .appendTo($el);
      }

      return $el;
    };

    Plugin.prototype.open = function() {
      this.$overlay
      .fadeTo(this.options.duration, this.options.overlay);

      this.$el
      .css({
        marginTop: this.options.top? 0 : this.$el.outerHeight() / -2 + 'px',
        marginLeft: this.$el.outerWidth() / -2 + 'px'
      })
      .fadeIn(this.options.duration, $.proxy(this.options.onOpen, this.element));
    };

    Plugin.prototype.close = function() {
      this.$overlay
      .fadeOut(this.options.duration);

      this.$el
      .fadeOut(this.options.duration, $.proxy(this.options.onClose, this.element));
    };

    // Expose the Plugin externaly
    return Plugin;

  })();

  // JQUERY --------------------------------------------------------------------
  //
  // Expose the plugin in the jQury prototype
  //
  $.fn[namespace] = function(options) {
    // Explode arguments tio use it when callbacking
    var args, _;
    _ = arguments[0];
    args = [].slice.call(arguments, 1);

    // Iterate over jQuery elements
    return this.each(function() {
      var plugin;

      // Get the plugin attached to the element
      plugin = $.data(this, "plugin_" + namespace);

      if(!plugin) {
        // Instanciate a new plugin and store it in the jQuery object
        $.data(this, "plugin_" + namespace, new Plugin(this, options));
      } else if(plugin[_] != null && $.type(plugin[_]) == 'function') {
        // If the plugin contains the called method, apply it
        return plugin[_].apply(plugin, args);
      }
    });
  };

})(jQuery);
<commit_msg>Fix el scope<commit_after>(function($, undefined) {
  'use strict';

  var Plugin, defaults, namespace;

  // Default options and settings
  namespace ='simplemodal';
  defaults = {
    top: null,
    overlay: 0.5,
    closeButton: null,
    duration: 200,
    onOpen: function() {},
    onClose: function() {}
  };

  // PLUGIN --------------------------------------------------------------------
  //
  // Declare the plugin fuction, isolated in a sub-module pattern to prevent
  // leaks and preserve vars scope.
  //
  Plugin = (function() {

    // The Plugin object
    function Plugin(element, options) {
      // Store elements to access it later easily
      this.element = element;
      this.options = $.extend({}, defaults, options);
      this._defaults = defaults;
      this._name = namespace;

      // Fire the init method
      this.init();
    }

    Plugin.prototype.init = function() {
      this.$overlay = setOverlay.call(this);
      this.$el = setModal.call(this, this.el);
    };

    var setOverlay = function () {
      return $('<div/>', {'class': 'sm-overlay'})
      .css({
        position: 'fixed',
        zIndex: 100,
        top: 0,
        left: 0,
        height: '100%',
        width: '100%',
        background: '#000'
      })
      .hide()
      .appendTo('body');
    };

    var setModal = function (el) {
      var $el = $('<div/>', {'class': 'sm-modal'})
      .css({
        position : 'fixed',
        zIndex: 101,
        top: this.options.top? this.options.top : '50%',
        left : '50%'
      })
      .hide()
      .appendTo('body');

      $('<div/>', {'class': 'sm-content'})
      .append($(el))
      .appendTo($el);

      if (this.options.closeButton) {
        $('<button/>', {'class': 'sm-close'})
        .on('click', $.proxy(this.close, this))
        .appendTo($el);
      }

      return $el;
    };

    Plugin.prototype.open = function() {
      this.$overlay
      .fadeTo(this.options.duration, this.options.overlay);

      this.$el
      .css({
        marginTop: this.options.top? 0 : this.$el.outerHeight() / -2 + 'px',
        marginLeft: this.$el.outerWidth() / -2 + 'px'
      })
      .fadeIn(this.options.duration, $.proxy(this.options.onOpen, this.element));
    };

    Plugin.prototype.close = function() {
      this.$overlay
      .fadeOut(this.options.duration);

      this.$el
      .fadeOut(this.options.duration, $.proxy(this.options.onClose, this.element));
    };

    // Expose the Plugin externaly
    return Plugin;

  })();

  // JQUERY --------------------------------------------------------------------
  //
  // Expose the plugin in the jQury prototype
  //
  $.fn[namespace] = function(options) {
    // Explode arguments tio use it when callbacking
    var args, _;
    _ = arguments[0];
    args = [].slice.call(arguments, 1);

    // Iterate over jQuery elements
    return this.each(function() {
      var plugin;

      // Get the plugin attached to the element
      plugin = $.data(this, "plugin_" + namespace);

      if(!plugin) {
        // Instanciate a new plugin and store it in the jQuery object
        $.data(this, "plugin_" + namespace, new Plugin(this, options));
      } else if(plugin[_] != null && $.type(plugin[_]) == 'function') {
        // If the plugin contains the called method, apply it
        return plugin[_].apply(plugin, args);
      }
    });
  };

})(jQuery);
<|endoftext|>


<commit_before>(function($, undefined) {
  'use strict';

  var Plugin, defaults, namespace;

  // Default options and settings
  namespace ='simplemodal';
  defaults = {
    top: null,
    overlay: 0.5,
    closeButton: null,
    className: null,
    duration: 200,
    onOpen: function() {},
    onClose: function() {}
  };

  // PLUGIN --------------------------------------------------------------------
  //
  // Declare the plugin fuction, isolated in a sub-module pattern to prevent
  // leaks and preserve vars scope.
  //
  Plugin = (function() {

    // The Plugin object
    function Plugin(element, options) {
      // Store elements to access it later easily
      this.element = element;
      this.options = $.extend({}, defaults, options);
      this._defaults = defaults;
      this._name = namespace;

      // Fire the init method
      this.init();
    }

    Plugin.prototype.init = function() {
      this.$overlay = setOverlay.call(this);
      this.$el = setModal.call(this);
    };

    var setOverlay = function () {
      var $el = $('<div/>', {'class': 'sm-overlay'})
      .css({
        position: 'fixed',
        zIndex: 9999,
        top: 0,
        left: 0,
        height: '100%',
        width: '100%',
        background: '#000'
      })
      .hide()
      .appendTo('body');

      $('<div/>', {'class': 'sm-loader'})
      .hide()
      .appendTo($el);

      return $el;
    };

    var setModal = function (el) {
      var $el,
          className = 'sm-modal' + (this.options.className? ' ' + this.options.className : '');

      $el = $('<div/>', {'class': className})
      .css({
        position : 'fixed',
        zIndex: 9999,
        top: this.options.top? this.options.top : '50%',
        left : '50%'
      })
      .hide()
      .appendTo('body');

      $('<div/>', {'class': 'sm-content'})
      .append( $(this.element) )
      .appendTo($el);

      if (this.options.closeButton) {
        $('<button/>', {'class': 'sm-close', 'text': 'close'})
        .on('click', $.proxy(this.close, this))
        .appendTo($el);
      }

      return $el;
    };

    Plugin.prototype.open = function() {
      this.$overlay
      .fadeTo(this.options.duration, this.options.overlay, function () {
        this.$overlay.find('.sm-loader').hide();
      });

      this.$el
      .css({
        marginTop: this.options.top? 0 : this.$el.outerHeight() / -2 + 'px',
        marginLeft: this.$el.outerWidth() / -2 + 'px'
      })
      .fadeIn(this.options.duration, $.proxy(this.options.onOpen, this.element));
    };

    Plugin.prototype.close = function() {
      this.$overlay
      .fadeOut(this.options.duration, function () {
        this.$overlay.find('.sm-loader').show();
      });

      this.$el
      .fadeOut(this.options.duration, $.proxy(this.options.onClose, this.element));
    };

    // Expose the Plugin externaly
    return Plugin;

  })();

  // JQUERY --------------------------------------------------------------------
  //
  // Expose the plugin in the jQury prototype
  //
  $.fn[namespace] = function(options) {
    // Explode arguments tio use it when callbacking
    var args, _;
    _ = arguments[0];
    args = [].slice.call(arguments, 1);

    // Iterate over jQuery elements
    return this.each(function() {
      var plugin;

      // Get the plugin attached to the element
      plugin = $.data(this, "plugin_" + namespace);

      if(!plugin) {
        // Instanciate a new plugin and store it in the jQuery object
        $.data(this, "plugin_" + namespace, new Plugin(this, options));
      } else if(plugin[_] != null && $.type(plugin[_]) == 'function') {
        // If the plugin contains the called method, apply it
        return plugin[_].apply(plugin, args);
      }
    });
  };

})(jQuery);
<commit_msg>Wrap close button text in a span to get availability to mask it<commit_after>(function($, undefined) {
  'use strict';

  var Plugin, defaults, namespace;

  // Default options and settings
  namespace ='simplemodal';
  defaults = {
    top: null,
    overlay: 0.5,
    closeButton: null,
    className: null,
    duration: 200,
    onOpen: function() {},
    onClose: function() {}
  };

  // PLUGIN --------------------------------------------------------------------
  //
  // Declare the plugin fuction, isolated in a sub-module pattern to prevent
  // leaks and preserve vars scope.
  //
  Plugin = (function() {

    // The Plugin object
    function Plugin(element, options) {
      // Store elements to access it later easily
      this.element = element;
      this.options = $.extend({}, defaults, options);
      this._defaults = defaults;
      this._name = namespace;

      // Fire the init method
      this.init();
    }

    Plugin.prototype.init = function() {
      this.$overlay = setOverlay.call(this);
      this.$el = setModal.call(this);
    };

    var setOverlay = function () {
      var $el = $('<div/>', {'class': 'sm-overlay'})
      .css({
        position: 'fixed',
        zIndex: 9999,
        top: 0,
        left: 0,
        height: '100%',
        width: '100%',
        background: '#000'
      })
      .hide()
      .appendTo('body');

      $('<div/>', {'class': 'sm-loader'})
      .hide()
      .appendTo($el);

      return $el;
    };

    var setModal = function (el) {
      var $el,
          className = 'sm-modal' + (this.options.className? ' ' + this.options.className : '');

      $el = $('<div/>', {'class': className})
      .css({
        position : 'fixed',
        zIndex: 9999,
        top: this.options.top? this.options.top : '50%',
        left : '50%'
      })
      .hide()
      .appendTo('body');

      $('<div/>', {'class': 'sm-content'})
      .append( $(this.element) )
      .appendTo($el);

      if (this.options.closeButton) {
        $('<button/>', {'class': 'sm-close'})
        .append( $('<span/>', {'text': 'close'}) )
        .on('click', $.proxy(this.close, this))
        .appendTo($el);
      }

      return $el;
    };

    Plugin.prototype.open = function() {
      this.$overlay
      .fadeTo(this.options.duration, this.options.overlay, function () {
        this.$overlay.find('.sm-loader').hide();
      });

      this.$el
      .css({
        marginTop: this.options.top? 0 : this.$el.outerHeight() / -2 + 'px',
        marginLeft: this.$el.outerWidth() / -2 + 'px'
      })
      .fadeIn(this.options.duration, $.proxy(this.options.onOpen, this.element));
    };

    Plugin.prototype.close = function() {
      this.$overlay
      .fadeOut(this.options.duration, function () {
        this.$overlay.find('.sm-loader').show();
      });

      this.$el
      .fadeOut(this.options.duration, $.proxy(this.options.onClose, this.element));
    };

    // Expose the Plugin externaly
    return Plugin;

  })();

  // JQUERY --------------------------------------------------------------------
  //
  // Expose the plugin in the jQury prototype
  //
  $.fn[namespace] = function(options) {
    // Explode arguments tio use it when callbacking
    var args, _;
    _ = arguments[0];
    args = [].slice.call(arguments, 1);

    // Iterate over jQuery elements
    return this.each(function() {
      var plugin;

      // Get the plugin attached to the element
      plugin = $.data(this, "plugin_" + namespace);

      if(!plugin) {
        // Instanciate a new plugin and store it in the jQuery object
        $.data(this, "plugin_" + namespace, new Plugin(this, options));
      } else if(plugin[_] != null && $.type(plugin[_]) == 'function') {
        // If the plugin contains the called method, apply it
        return plugin[_].apply(plugin, args);
      }
    });
  };

})(jQuery);
<|endoftext|>


<commit_before>/*!
 * $ Simple Modal plugin
 * Author: John Deerhake, @jdeerhake
 * Version 0.0.3
 * Licensed under the MIT license
 */

;(function ( $ ) {
  var backdrop,
    FADE_TIME = 300,
    defaults = {
      "class" : "simple-modal-box"
    };

  function createBackdrop() {
    backdrop = $("<div />", { "class" : "simple-modal-backdrop" }).appendTo("body");
  }

  function Modal ( el, settings ) {
    $.extend( this, settings );
    this.el = $( el );
    this.isShown = false;
    this.init();
  }

  Modal.prototype = {
    init : function () {
      if(!backdrop) {
        createBackdrop();
      }
      this.modal = {};
      this.modal.el = $("<div />", { "class" :  this["class"] }).appendTo("body");
      this.modal.el.append(this.el.remove().show());
      this.bindings();
    },
    bindings : function () {
      this.el.on( "show", $.proxy( this, "show" ) );
      this.el.on( "hide", $.proxy( this, "hide" ) );

      var triggerShow = $.proxy( function() {
        this.el.trigger("show");
        return false;
      }, this ),
      triggerHide = $.proxy( function() {
        this.el.trigger("hide");
        return false;
      }, this );

      backdrop.on( "click", triggerHide );
      this.el.on( "click", ".close", triggerHide );

      $( document ).on( "keyup", function(ev) {
        if( ev.keyCode === 27 ) { triggerHide(); } /*ESC*/
      });

      if( this.trigger ) {
        this.trigger.on( "click", triggerShow );
      }

      this.el.find(".close").on( "click" );
    },
    show : function () {
      if( this.isShown ) { return false; }
      this.isShown = true;
      backdrop.show();
      this.modal.el.css({ opacity : 0 }).show();
      this.centerer = setInterval( $.proxy( this, "center" ), 500 );

      setTimeout( $.proxy( function() {
        this.center();
        this.modal.el.animate({ opacity : 1 }, FADE_TIME);
      }, this ), 50);
      return false;
    },
    hide : function () {
      if( !this.isShown ) { return false; }
      this.isShown = false;
      clearInterval( this.centerer );
      backdrop.fadeOut(FADE_TIME);
      this.modal.el.fadeOut(FADE_TIME, $.proxy( function () {
        this.el.trigger("hidden");
      }, this ));
      return false;
    },
    center : function () {
      var width = Math.floor(this.modal.el.outerWidth()),
        height = Math.floor(this.modal.el.outerHeight());

      if(width != this.modal.width || height != this.modal.height) {
        this.modal.el.css({
          marginTop : Math.floor(height / -2),
          marginLeft : Math.floor(width / -2)
        });
        this.modal.width = width;
        this.modal.height = height;
      }
    }
  };

  $.fn.simpleModal = function ( options ) {
    var settings = $.extend({}, defaults, options);
    return this.each(function() {
      new Modal ( this, settings );
    });
  };
})( jQuery );<commit_msg>Version bump<commit_after>/*!
 * $ Simple Modal plugin
 * Author: John Deerhake, @jdeerhake
 * Version 0.0.4
 * Licensed under the MIT license
 */

;(function ( $ ) {
  var backdrop,
    FADE_TIME = 300,
    defaults = {
      "class" : "simple-modal-box"
    };

  function createBackdrop() {
    backdrop = $("<div />", { "class" : "simple-modal-backdrop" }).appendTo("body");
  }

  function Modal ( el, settings ) {
    $.extend( this, settings );
    this.el = $( el );
    this.isShown = false;
    this.init();
  }

  Modal.prototype = {
    init : function () {
      if(!backdrop) {
        createBackdrop();
      }
      this.modal = {};
      this.modal.el = $("<div />", { "class" :  this["class"] }).appendTo("body");
      this.modal.el.append(this.el.remove().show());
      this.bindings();
    },
    bindings : function () {
      this.el.on( "show", $.proxy( this, "show" ) );
      this.el.on( "hide", $.proxy( this, "hide" ) );

      var triggerShow = $.proxy( function() {
        this.el.trigger("show");
        return false;
      }, this ),
      triggerHide = $.proxy( function() {
        this.el.trigger("hide");
        return false;
      }, this );

      backdrop.on( "click", triggerHide );
      this.el.on( "click", ".close", triggerHide );

      $( document ).on( "keyup", function(ev) {
        if( ev.keyCode === 27 ) { triggerHide(); } /*ESC*/
      });

      if( this.trigger ) {
        this.trigger.on( "click", triggerShow );
      }

      this.el.find(".close").on( "click" );
    },
    show : function () {
      if( this.isShown ) { return false; }
      this.isShown = true;
      backdrop.show();
      this.modal.el.css({ opacity : 0 }).show();
      this.centerer = setInterval( $.proxy( this, "center" ), 500 );

      setTimeout( $.proxy( function() {
        this.center();
        this.modal.el.animate({ opacity : 1 }, FADE_TIME);
      }, this ), 50);
      return false;
    },
    hide : function () {
      if( !this.isShown ) { return false; }
      this.isShown = false;
      clearInterval( this.centerer );
      backdrop.fadeOut(FADE_TIME);
      this.modal.el.fadeOut(FADE_TIME, $.proxy( function () {
        this.el.trigger("hidden");
      }, this ));
      return false;
    },
    center : function () {
      var width = Math.floor(this.modal.el.outerWidth()),
        height = Math.floor(this.modal.el.outerHeight());

      if(width != this.modal.width || height != this.modal.height) {
        this.modal.el.css({
          marginTop : Math.floor(height / -2),
          marginLeft : Math.floor(width / -2)
        });
        this.modal.width = width;
        this.modal.height = height;
      }
    }
  };

  $.fn.simpleModal = function ( options ) {
    var settings = $.extend({}, defaults, options);
    return this.each(function() {
      new Modal ( this, settings );
    });
  };
})( jQuery );
<|endoftext|>


<commit_before>(function(e,t){"use strict";function s(e){var t=Array.prototype.slice.call(arguments,1);return e.prop?e.prop.apply(e,t):e.attr.apply(e,t)}function n(e,t,s){var n,a;for(n in s)s.hasOwnProperty(n)&&(a=n.replace(/ |$/g,t.eventNamespace),e.bind(n,s[n]))}function a(e,t,s){n(e,s,{focus:function(){t.addClass(s.focusClass)},blur:function(){t.removeClass(s.focusClass),t.removeClass(s.activeClass)},mouseenter:function(){t.addClass(s.hoverClass)},mouseleave:function(){t.removeClass(s.hoverClass),t.removeClass(s.activeClass)},"mousedown touchbegin":function(){e.is(":disabled")||t.addClass(s.activeClass)},"mouseup touchend":function(){t.removeClass(s.activeClass)}})}function i(e,t){e.removeClass(t.hoverClass+" "+t.focusClass+" "+t.activeClass)}function r(e,t,s){s?e.addClass(t):e.removeClass(t)}function u(e,t,s){var n="checked",a=t.is(":"+n);t.prop?t.prop(n,a):a?t.attr(n,n):t.removeAttr(n),r(e,s.checkedClass,a)}function l(e,t,s){r(e,s.disabledClass,t.is(":disabled"))}function o(e,t,s){switch(s){case"after":return e.after(t),e.next();case"before":return e.before(t),e.prev();case"wrap":return e.wrap(t),e.parent()}return null}function c(t,n,a){var i,r,u;return a||(a={}),a=e.extend({bind:{},css:null,divClass:null,divWrap:"wrap",spanClass:null,spanHtml:null,spanWrap:"wrap"},a),i=e("<div />"),r=e("<span />"),n.autoHide&&!t.is(":visible")&&i.hide(),a.divClass&&i.addClass(a.divClass),a.spanClass&&r.addClass(a.spanClass),u=s(t,"id"),n.useID&&u&&s(i,"id",n.idPrefix+"-"+u),a.spanHtml&&r.html(a.spanHtml),i=o(t,i,a.divWrap),r=o(t,r,a.spanWrap),a.css&&t.css(a.css),l(i,t,n),{div:i,span:r}}function f(t){return t?e("<span />").text(t).html():""}function d(e){var t;return e[0].multiple?!0:(t=s(e,"size"),!t||1>=t?!1:!0)}function m(){return!1}function p(e,t){var s="none";n(e,t,{"selectstart dragstart mousedown":m}),e.css({MozUserSelect:s,msUserSelect:s,webkitUserSelect:s,userSelect:s})}function v(e,t,s){var n=e.val();""===n?n=s.fileDefaultHtml:(n=n.split(/[\/\\]+/),n=n[n.length-1]),t.text(n)}function h(e,t){return function(){e.unwrap().unwrap().unbind(t.eventNamespace)}}var C=!0,b=[{match:function(e){return e.is("a, button, :submit, :reset, input[type='button']")},apply:function(t,r){var u,o,d,m;return o=r.submitDefaultHtml,t.is(":reset")&&(o=r.resetDefaultHtml),m=t.is("a, button")?function(){return t.html()||o}:function(){return f(s(t,"value"))||o},d=c(t,r,{css:{display:"none"},divClass:r.buttonClass,spanHtml:m()}),u=d.div,a(t,u,r),n(u,r,{"click touchend":function(n){var a,i,r,u;e(n.target).is("span, div")&&(t[0].dispatchEvent?(a=document.createEvent("MouseEvents"),a.initEvent("click",!0,!0),i=t[0].dispatchEvent(a),(jQuery.browser.msie||jQuery.browser.mozilla)&&t.is("a")&&i&&(r=s(t,"target"),u=s(t,"href"),r&&"_self"!==r?window.open(u,r):document.location.href=u)):t.click())}}),p(u,r),{remove:h(t,r),update:function(){i(u,r),l(u,t,r),d.span.html(m())}}}},{match:function(e){return e.is(":checkbox")},apply:function(e,t){var s,r,o;return s=c(e,t,{css:{opacity:0},divClass:t.checkboxClass}),r=s.div,o=s.span,a(e,r,t),n(e,t,{"click touchend":function(){u(o,e,t)}}),u(o,e,t),{remove:h(e,t),update:function(){i(r,t),o.removeClass(t.checkedClass),u(o,e,t),l(r,e,t)}}}},{match:function(e){return e.is(":file")},apply:function(t,r){function u(){v(t,m,r)}var f,d,m,h;return f=c(t,r,{css:{opacity:0},divClass:r.fileClass,spanClass:r.fileButtonClass,spanHtml:r.fileButtonHtml,spanWrap:"after"}),d=f.div,h=f.span,m=e("<span />").html(r.fileDefaultHtml),m.addClass(r.filenameClass),m=o(t,m,"after"),s(t,"size")||s(t,"size",d.width()/10),a(t,d,r),u(),e.browser.msie?n(t,r,{click:function(){t.trigger("change"),setTimeout(u,0)}}):n(t,r,{change:u}),p(m,r),p(h,r),{remove:function(){return m.remove(),h.remove(),t.unwrap().unbind(r.eventNamespace)},update:function(){i(d,r),v(t,m,r),l(d,t,r)}}}},{match:function(e){if(e.is("input")){var t=(" "+s(e,"type")+" ").toLowerCase(),n=" color date datetime datetime-local email month number password search tel text time url week ";return n.indexOf(t)>=0}return!1},apply:function(e){var t=s(e,"type");return e.addClass(t),{remove:function(){e.removeClass(t)},update:m}}},{match:function(e){return e.is(":radio")},apply:function(t,r){var o,f,d;return o=c(t,r,{css:{opacity:0},divClass:r.radioClass}),f=o.div,d=o.span,a(t,f,r),n(t,r,{"click touchend":function(){e(":radio:has([name='"+s(t,"name")+"'])").uniform.update()}}),u(d,t,r),{remove:h(t,r),update:function(){i(f,r),u(d,t,r),l(f,t,r)}}}},{match:function(e){return e.is("select")&&!d(e)?!0:!1},apply:function(e,t){var s,r,u,o,f;return o=e.width(),s=c(e,t,{divClass:t.selectClass,spanHtml:(e.find(":selected:first")||e.find("option:first")).html(),spanWrap:"before"}),r=s.div,u=s.span,f=u.outerWidth()-u.width(),t.selectAutoWidth?(r.width(o+f),u.width(r.width()-f)):r.addClass("fixedWidth"),a(e,r,t),n(e,t,{change:function(){u.html(e.find(":selected").html()),r.removeClass(t.activeClass)},"click touchend":function(){var t=e.find(":selected").html();u.html()!==t&&e.trigger("change")},keyup:function(){u.html(e.find(":selected").html())}}),p(u,t),{remove:function(){return u.remove(),e.unwrap().unbind(t.eventNamespace),e},update:function(){i(r,t),u.html(e.find(":selected").html()),l(r,e,t)}}}},{match:function(e){return e.is("select")&&d(e)?!0:!1},apply:function(e,t){return e.addClass(t.selectMultiClass),{remove:function(){e.removeClass(t.selectMultiClass)},update:m}}},{match:function(e){return e.is("textarea")},apply:function(e,t){return e.addClass(t.textareaClass),{remove:function(){e.removeClass(t.textareaClass)},update:m}}}];e.browser.msie&&7>e.browser.version&&(C=!1),e.uniform={defaults:{activeClass:"active",autoHide:!0,buttonClass:"button",checkboxClass:"checker",checkedClass:"checked",disabledClass:"disabled",eventNamespace:".uniform",fileButtonClass:"action",fileButtonHtml:"Choose File",fileClass:"uploader",fileDefaultHtml:"No file selected",filenameClass:"filename",focusClass:"focus",hoverClass:"hover",idPrefix:"uniform",radioClass:"radio",resetDefaultHtml:"Reset",resetSelector:!1,selectAutoWidth:!0,selectClass:"selector",selectMultiClass:"uniform-multiselect",submitDefaultHtml:"Submit",textareaClass:"uniform",useID:!0},elements:[]},e.fn.uniform=function(t){var s=this;return t=e.extend({},e.uniform.defaults,t),t.resetSelector&&e(t.resetSelector).mouseup(function(){window.setTimeout(function(){e.uniform.update(s)},10)}),this.each(function(){var s,n,a,i=e(this);if(i.data("uniformed"))e.uniform.update(i);else if(C)for(s=0;b.length>s;s+=1)if(n=b[s],n.match(i,t))return a=n.apply(i,t),i.data("uniformed",a),e.uniform.elements.push(i.get(0)),void 0})},e.uniform.restore=e.fn.uniform.restore=function(s){s===t&&(s=e.uniform.elements),e(s).each(function(){var t,s,n=e(this);s=n.data("uniformed"),s&&(s.remove(),t=e.inArray(this,e.uniform.elements),t>=0&&e.uniform.elements.splice(t,1),n.removeData("uniformed"))})},e.uniform.update=e.fn.uniform.update=function(s){s===t&&(s=e.uniform.elements),e(s).each(function(){var t,s=e(this);t=s.data("uniformed"),t&&t.update(s,t.options)})}})(jQuery);<commit_msg>Updating to match the library<commit_after>(function(e,t){"use strict";function s(e){var t=Array.prototype.slice.call(arguments,1);return e.prop?e.prop.apply(e,t):e.attr.apply(e,t)}function n(e,t,s){var n,a;for(n in s)s.hasOwnProperty(n)&&(a=n.replace(/ |$/g,t.eventNamespace),e.bind(n,s[n]))}function a(e,t,s){n(e,s,{focus:function(){t.addClass(s.focusClass)},blur:function(){t.removeClass(s.focusClass),t.removeClass(s.activeClass)},mouseenter:function(){t.addClass(s.hoverClass)},mouseleave:function(){t.removeClass(s.hoverClass),t.removeClass(s.activeClass)},"mousedown touchbegin":function(){e.is(":disabled")||t.addClass(s.activeClass)},"mouseup touchend":function(){t.removeClass(s.activeClass)}})}function i(e,t){e.removeClass(t.hoverClass+" "+t.focusClass+" "+t.activeClass)}function r(e,t,s){s?e.addClass(t):e.removeClass(t)}function o(e,t,s){var n="checked",a=t.is(":"+n);t.prop?t.prop(n,a):a?t.attr(n,n):t.removeAttr(n),r(e,s.checkedClass,a)}function l(e,t,s){r(e,s.disabledClass,t.is(":disabled"))}function u(t){var s;return s=t.clone(),s.css("visibility","hidden"),e("body").append(s),s}function c(e,t,s){switch(s){case"after":return e.after(t),e.next();case"before":return e.before(t),e.prev();case"wrap":return e.wrap(t),e.parent()}return null}function f(t,n,a){var i,r,o;return a||(a={}),a=e.extend({bind:{},css:null,divClass:null,divWrap:"wrap",spanClass:null,spanHtml:null,spanWrap:"wrap"},a),i=e("<div />"),r=e("<span />"),n.autoHide&&!t.is(":visible")&&i.hide(),a.divClass&&i.addClass(a.divClass),a.spanClass&&r.addClass(a.spanClass),o=s(t,"id"),n.useID&&o&&s(i,"id",n.idPrefix+"-"+o),a.spanHtml&&r.html(a.spanHtml),i=c(t,i,a.divWrap),r=c(t,r,a.spanWrap),a.css&&t.css(a.css),l(i,t,n),{div:i,span:r}}function d(t){return t?e("<span />").text(t).html():""}function m(e){var t;return e[0].multiple?!0:(t=s(e,"size"),!t||1>=t?!1:!0)}function p(){return!1}function v(e,t){var s="none";n(e,t,{"selectstart dragstart mousedown":p}),e.css({MozUserSelect:s,msUserSelect:s,webkitUserSelect:s,userSelect:s})}function h(e,t,s){var n=e.val();""===n?n=s.fileDefaultHtml:(n=n.split(/[\/\\]+/),n=n[n.length-1]),t.text(n)}function C(e,t){return function(){e.unwrap().unwrap().unbind(t.eventNamespace)}}var b=!0,w=[{match:function(e){return e.is("a, button, :submit, :reset, input[type='button']")},apply:function(t,r){var o,u,c,m;return u=r.submitDefaultHtml,t.is(":reset")&&(u=r.resetDefaultHtml),m=t.is("a, button")?function(){return t.html()||u}:function(){return d(s(t,"value"))||u},c=f(t,r,{css:{display:"none"},divClass:r.buttonClass,spanHtml:m()}),o=c.div,a(t,o,r),n(o,r,{"click touchend":function(n){var a,i,r,o;e(n.target).is("span, div")&&(t[0].dispatchEvent?(a=document.createEvent("MouseEvents"),a.initEvent("click",!0,!0),i=t[0].dispatchEvent(a),(jQuery.browser.msie||jQuery.browser.mozilla)&&t.is("a")&&i&&(r=s(t,"target"),o=s(t,"href"),r&&"_self"!==r?window.open(o,r):document.location.href=o)):t.click())}}),v(o,r),{remove:C(t,r),update:function(){i(o,r),l(o,t,r),c.span.html(m())}}}},{match:function(e){return e.is(":checkbox")},apply:function(e,t){var s,r,u;return s=f(e,t,{css:{opacity:0},divClass:t.checkboxClass}),r=s.div,u=s.span,a(e,r,t),n(e,t,{"click touchend":function(){o(u,e,t)}}),o(u,e,t),{remove:C(e,t),update:function(){i(r,t),u.removeClass(t.checkedClass),o(u,e,t),l(r,e,t)}}}},{match:function(e){return e.is(":file")},apply:function(t,r){function o(){h(t,m,r)}var u,d,m,p;return u=f(t,r,{css:{opacity:0},divClass:r.fileClass,spanClass:r.fileButtonClass,spanHtml:r.fileButtonHtml,spanWrap:"after"}),d=u.div,p=u.span,m=e("<span />").html(r.fileDefaultHtml),m.addClass(r.filenameClass),m=c(t,m,"after"),s(t,"size")||s(t,"size",d.width()/10),a(t,d,r),o(),e.browser.msie?n(t,r,{click:function(){t.trigger("change"),setTimeout(o,0)}}):n(t,r,{change:o}),v(m,r),v(p,r),{remove:function(){return m.remove(),p.remove(),t.unwrap().unbind(r.eventNamespace)},update:function(){i(d,r),h(t,m,r),l(d,t,r)}}}},{match:function(e){if(e.is("input")){var t=(" "+s(e,"type")+" ").toLowerCase(),n=" color date datetime datetime-local email month number password search tel text time url week ";return n.indexOf(t)>=0}return!1},apply:function(e){var t=s(e,"type");return e.addClass(t),{remove:function(){e.removeClass(t)},update:p}}},{match:function(e){return e.is(":radio")},apply:function(t,r){var u,c,d;return u=f(t,r,{css:{opacity:0},divClass:r.radioClass}),c=u.div,d=u.span,a(t,c,r),n(t,r,{"click touchend":function(){e(":radio:has([name='"+s(t,"name")+"'])").uniform.update()}}),o(d,t,r),{remove:C(t,r),update:function(){i(c,r),o(d,t,r),l(c,t,r)}}}},{match:function(e){return e.is("select")&&!m(e)?!0:!1},apply:function(e,t){var s,r,o,c,d,m,p,h,C;return C=e.is(":hidden"),C?(s=u(e),p=s.width(),s.remove()):p=e.width(),r=f(e,t,{divClass:t.selectClass,spanHtml:(e.find(":selected:first")||e.find("option:first")).html(),spanWrap:"before"}),o=r.div,c=r.span,C?(s=u(o),d=s,m=s.children().first()):(d=o,m=c),h=m.outerWidth()-m.width(),t.selectAutoWidth?(o.width(p+h),c.width(d.width()-h)):o.addClass("fixedWidth"),C&&s.remove(),a(e,o,t),n(e,t,{change:function(){c.html(e.find(":selected").html()),o.removeClass(t.activeClass)},"click touchend":function(){var t=e.find(":selected").html();c.html()!==t&&e.trigger("change")},keyup:function(){c.html(e.find(":selected").html())}}),v(c,t),{remove:function(){return c.remove(),e.unwrap().unbind(t.eventNamespace),e},update:function(){i(o,t),c.html(e.find(":selected").html()),l(o,e,t)}}}},{match:function(e){return e.is("select")&&m(e)?!0:!1},apply:function(e,t){return e.addClass(t.selectMultiClass),{remove:function(){e.removeClass(t.selectMultiClass)},update:p}}},{match:function(e){return e.is("textarea")},apply:function(e,t){return e.addClass(t.textareaClass),{remove:function(){e.removeClass(t.textareaClass)},update:p}}}];e.browser.msie&&7>e.browser.version&&(b=!1),e.uniform={defaults:{activeClass:"active",autoHide:!0,buttonClass:"button",checkboxClass:"checker",checkedClass:"checked",disabledClass:"disabled",eventNamespace:".uniform",fileButtonClass:"action",fileButtonHtml:"Choose File",fileClass:"uploader",fileDefaultHtml:"No file selected",filenameClass:"filename",focusClass:"focus",hoverClass:"hover",idPrefix:"uniform",radioClass:"radio",resetDefaultHtml:"Reset",resetSelector:!1,selectAutoWidth:!0,selectClass:"selector",selectMultiClass:"uniform-multiselect",submitDefaultHtml:"Submit",textareaClass:"uniform",useID:!0},elements:[]},e.fn.uniform=function(t){var s=this;return t=e.extend({},e.uniform.defaults,t),t.resetSelector&&e(t.resetSelector).mouseup(function(){window.setTimeout(function(){e.uniform.update(s)},10)}),this.each(function(){var s,n,a,i=e(this);if(i.data("uniformed"))e.uniform.update(i);else if(b)for(s=0;w.length>s;s+=1)if(n=w[s],n.match(i,t))return a=n.apply(i,t),i.data("uniformed",a),e.uniform.elements.push(i.get(0)),void 0})},e.uniform.restore=e.fn.uniform.restore=function(s){s===t&&(s=e.uniform.elements),e(s).each(function(){var t,s,n=e(this);s=n.data("uniformed"),s&&(s.remove(),t=e.inArray(this,e.uniform.elements),t>=0&&e.uniform.elements.splice(t,1),n.removeData("uniformed"))})},e.uniform.update=e.fn.uniform.update=function(s){s===t&&(s=e.uniform.elements),e(s).each(function(){var t,s=e(this);t=s.data("uniformed"),t&&t.update(s,t.options)})}})(jQuery);<|endoftext|>


<commit_before>// Copyright 2019-2021, University of Colorado Boulder

/**
 * The popup list box for a ComboBox.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

import Action from '../../axon/js/Action.js';
import merge from '../../phet-core/js/merge.js';
import KeyboardUtils from '../../scenery/js/accessibility/KeyboardUtils.js';
import SceneryEvent from '../../scenery/js/input/SceneryEvent.js';
import VBox from '../../scenery/js/nodes/VBox.js';
import multiSelectionSoundPlayerFactory from '../../tambo/js/multiSelectionSoundPlayerFactory.js';
import generalCloseSoundPlayer from '../../tambo/js/shared-sound-players/generalCloseSoundPlayer.js';
import generalOpenSoundPlayer from '../../tambo/js/shared-sound-players/generalOpenSoundPlayer.js';
import EventType from '../../tandem/js/EventType.js';
import Tandem from '../../tandem/js/Tandem.js';
import ComboBoxListItemNode from './ComboBoxListItemNode.js';
import Panel from './Panel.js';
import sun from './sun.js';

class ComboBoxListBox extends Panel {

  /**
   * @param {Property} property
   * @param {ComboBoxItem[]} items
   * @param {function} hideListBoxCallback - called to hide the list box
   * @param {function} focusButtonCallback - called to transfer focus to the combo box's button
   * @param {Tandem} tandem
   * @param {Object} [options]
   */
  constructor( property, items, hideListBoxCallback, focusButtonCallback, tandem, options ) {

    options = merge( {

      // {Color|string} fill for the highlight behind items in the list
      highlightFill: 'rgb( 245, 245, 245 )',

      // Panel options
      xMargin: 12,
      yMargin: 8,
      backgroundPickable: true,

      // pdom
      tagName: 'ul',
      ariaRole: 'listbox',
      groupFocusHighlight: true,
      focusable: true,

      // {SoundPlayer} - Sound generators for when combo box is opened and when it is closed with no change. Closing *with*
      // a change is covered by individual combo box items.
      openedSoundPlayer: generalOpenSoundPlayer,
      closedNoChangeSoundPlayer: generalCloseSoundPlayer,
      visiblePropertyOptions: { phetioReadOnly: true }

      // Not instrumented for PhET-iO because the list's position isn't valid until it has been popped up.
      // See https://github.com/phetsims/phet-io/issues/1102
    }, options );

    assert && assert( options.xMargin > 0 && options.yMargin > 0,
      `margins must be > 0, xMargin=${options.xMargin}, yMargin=${options.yMargin}` );

    //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
    // Pops down the list box and sets the property.value to match the chosen item.
    const fireAction = new Action( event => {

      const listItemNode = event.currentTarget;
      assert && assert( listItemNode instanceof ComboBoxListItemNode, 'expected a ComboBoxListItemNode' );

      // set value based on which item was chosen in the list box
      property.value = listItemNode.item.value;

      // hide the list
      hideListBoxCallback();

      // prevent nodes (eg, controls) behind the list from receiving the event
      event.abort();

      focusButtonCallback();
    }, {
      parameters: [ { phetioPrivate: true, valueType: SceneryEvent } ],

      // phet-io
      tandem: tandem.createTandem( 'fireAction' ),
      phetioEventType: EventType.USER
    } );

    //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
    // Handles selection from the list box.
    const selectionListener = {

      up( event ) {
        fireAction.execute( event );
      },

      // Handle keyup on each item in the list box, for a11y.
      //TODO sun#447, scenery#931 we're using keyup because keydown fires continuously
      keyup: event => {
        if ( KeyboardUtils.isAnyKeyEvent( event.domEvent, [ KeyboardUtils.KEY_ENTER, KeyboardUtils.KEY_SPACE ] ) ) {
          fireAction.execute( event );
        }
      },

      // handle activation from an assistive device that may not use a keyboard (such as mobile VoiceOver)
      click: event => {
        fireAction.execute( event );
      }
    };

    // Compute max item dimensions
    const maxItemWidth = _.maxBy( items, item => item.node.width ).node.width;
    const maxItemHeight = _.maxBy( items, item => item.node.height ).node.height;

    // Uniform dimensions for all highlighted items in the list, highlight overlaps margin by 50%
    const highlightWidth = maxItemWidth + options.xMargin;
    const highlightHeight = maxItemHeight + options.yMargin;

    // Create a node for each item in the list, and attach a listener.
    const listItemNodes = []; // {ComboBoxListItemNode[]}
    items.forEach( ( item, index ) => {

      // Create the list item node
      const listItemNode = new ComboBoxListItemNode( item, highlightWidth, highlightHeight, {
        align: options.align,
        highlightFill: options.highlightFill,
        highlightCornerRadius: options.cornerRadius,

        // highlight overlaps half of margins
        xMargin: 0.5 * options.xMargin,
        left: 0.5 * options.xMargin,
        top: ( 0.5 * options.yMargin ) + ( index * highlightHeight ),
        tandem: item.tandemName ? tandem.createTandem( item.tandemName ) : Tandem.OPTIONAL
      } );
      listItemNodes.push( listItemNode );

      listItemNode.addInputListener( selectionListener );
    } );

    const content = new VBox( {
      spacing: 0,
      excludeInvisibleChildrenFromBounds: true,
      children: listItemNodes
    } );

    // Adjust margins to account for highlight overlap
    options.xMargin = options.xMargin / 2;
    options.yMargin = options.yMargin / 2;

    super( content, options );

    // variable for tracking whether the selected value was changed by the user
    let selectionWhenListBoxOpened;

    // Make a list of sound generators for the items, using defaults if nothing was provided.
    const itemSelectedSoundPlayers = items.map( item => {
      return item.soundPlayer ?
             item.soundPlayer :
             multiSelectionSoundPlayerFactory.getSelectionSoundPlayer( items.indexOf( item ) );
    } );

    // sound generation
    this.visibleProperty.lazyLink( visible => {

      if ( visible ) {

        // play the 'opened' sound
        options.openedSoundPlayer.play();

        // keep track of what was selected when the list box was shown
        selectionWhenListBoxOpened = property.value;
      }
      else {

        // sound generation - assumes that the property value has been updated before this list box is hidden
        assert && assert( selectionWhenListBoxOpened !== undefined, 'no value for property when list box opened' );
        if ( selectionWhenListBoxOpened === property.value ) {
          options.closedNoChangeSoundPlayer.play();
        }
        else {
          const indexOfSelection = items.findIndex( item => item.value === property.value );
          itemSelectedSoundPlayers[ indexOfSelection ].play();
        }
      }
    } );

    // @public {ComboBoxListItemNode|null} the ComboBoxListItemNode that has focus
    this.focusedItemNode = null;

    // pdom listener for the entire list box
    this.addInputListener( {

      // When the list box gets focus, transfer focus to the ComboBoxListItemNode that matches property.value.
      focus: event => {
        if ( this.visible ) {
          for ( let i = 0; i < listItemNodes.length; i++ ) {
            const listItemNode = listItemNodes[ i ];
            if ( property.value === listItemNode.item.value ) {
              this.focusedItemNode = listItemNode;
              this.focusedItemNode.focus();
              break;
            }
          }
        }
      },

      // Handle keydown
      keydown: event => {
        if ( KeyboardUtils.isAnyKeyEvent( event.domEvent, [ KeyboardUtils.KEY_ESCAPE, KeyboardUtils.KEY_TAB ] ) ) {

          // Escape and Tab hide the list box and return focus to the button
          hideListBoxCallback();
          focusButtonCallback();
        }
        else if ( KeyboardUtils.isAnyKeyEvent( event.domEvent, [ KeyboardUtils.KEY_DOWN_ARROW, KeyboardUtils.KEY_UP_ARROW ] ) ) {

          // prevent "native" behavior so that Safari doesn't make an error sound with arrow keys in
          // full screen mode, see #210
          event.domEvent.preventDefault();

          // Up/down arrow keys move the focus between items in the list box
          const direction = ( KeyboardUtils.isKeyEvent( event.domEvent, KeyboardUtils.KEY_DOWN_ARROW ) ) ? 1 : -1;
          for ( let i = 0; i < listItemNodes.length; i++ ) {
            if ( this.focusedItemNode === listItemNodes[ i ] ) {
              const nextListItemNode = listItemNodes[ i + direction ];
              if ( nextListItemNode ) {

                // set focus for next item
                this.focusedItemNode = nextListItemNode;
                this.focusedItemNode.focus();

                break;
              }
            }
          }

          // reserve for drag after focus has moved, as the change in focus will clear the intent
          // on the pointer
          event.pointer.reserveForKeyboardDrag();
        }
        else if ( KeyboardUtils.isKeyEvent( event.domEvent, KeyboardUtils.KEY_HOME ) ) {
          this.focusedItemNode = listItemNodes[ 0 ];
          this.focusedItemNode.focus();
        }
        else if ( KeyboardUtils.isKeyEvent( event.domEvent, KeyboardUtils.KEY_END ) ) {
          this.focusedItemNode = listItemNodes[ listItemNodes.length - 1 ];
          this.focusedItemNode.focus();
        }
      }
    } );

    // @private
    this.listItemNodes = listItemNodes;

    // @private
    this.disposeComboBoxListBox = () => {
      for ( let i = 0; i < listItemNodes; i++ ) {
        listItemNodes[ i ].dispose(); // to unregister tandem
      }
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeComboBoxListBox();
    super.dispose();
  }

  /**
   * Sets the visibility of one or more items in the listbox that correspond to a value. Assumes that each item
   * in the listbox has a unique value.
   * @param {*} value - the value associated with the ComboBoxItem
   * @param {boolean} visible
   * @public
   */
  setItemVisible( value, visible ) {
    this.getListItemNode( value ).visible = visible;
  }

  /**
   * Is the item that corresponds to a value visible when the listbox is popped up?
   * @param {*} value
   * @returns {boolean}
   * @public
   */
  isItemVisible( value ) {
    return this.getListItemNode( value ).visible;
  }

  /**
   * Gets the ComboBoxListItemNode that corresponds to a specified value. Assumes that values are unique.
   * @param {*} value
   * @returns {ComboBoxListItemNode}
   * @private
   */
  getListItemNode( value ) {
    const listItemNode = _.find( this.listItemNodes, listItemNode => listItemNode.item.value === value );
    assert && assert( listItemNode, `no item found for value: ${value}` );
    assert && assert( listItemNode instanceof ComboBoxListItemNode, 'invalid listItemNode' );
    return listItemNode;
  }
}

sun.register( 'ComboBoxListBox', ComboBoxListBox );
export default ComboBoxListBox;<commit_msg>only traverse visible items with keyboard, see https://github.com/phetsims/sun/issues/720<commit_after>// Copyright 2019-2021, University of Colorado Boulder

/**
 * The popup list box for a ComboBox.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

import Action from '../../axon/js/Action.js';
import merge from '../../phet-core/js/merge.js';
import KeyboardUtils from '../../scenery/js/accessibility/KeyboardUtils.js';
import SceneryEvent from '../../scenery/js/input/SceneryEvent.js';
import VBox from '../../scenery/js/nodes/VBox.js';
import multiSelectionSoundPlayerFactory from '../../tambo/js/multiSelectionSoundPlayerFactory.js';
import generalCloseSoundPlayer from '../../tambo/js/shared-sound-players/generalCloseSoundPlayer.js';
import generalOpenSoundPlayer from '../../tambo/js/shared-sound-players/generalOpenSoundPlayer.js';
import EventType from '../../tandem/js/EventType.js';
import Tandem from '../../tandem/js/Tandem.js';
import ComboBoxListItemNode from './ComboBoxListItemNode.js';
import Panel from './Panel.js';
import sun from './sun.js';

class ComboBoxListBox extends Panel {

  /**
   * @param {Property} property
   * @param {ComboBoxItem[]} items
   * @param {function} hideListBoxCallback - called to hide the list box
   * @param {function} focusButtonCallback - called to transfer focus to the combo box's button
   * @param {Tandem} tandem
   * @param {Object} [options]
   */
  constructor( property, items, hideListBoxCallback, focusButtonCallback, tandem, options ) {

    options = merge( {

      // {Color|string} fill for the highlight behind items in the list
      highlightFill: 'rgb( 245, 245, 245 )',

      // Panel options
      xMargin: 12,
      yMargin: 8,
      backgroundPickable: true,

      // pdom
      tagName: 'ul',
      ariaRole: 'listbox',
      groupFocusHighlight: true,
      focusable: true,

      // {SoundPlayer} - Sound generators for when combo box is opened and when it is closed with no change. Closing *with*
      // a change is covered by individual combo box items.
      openedSoundPlayer: generalOpenSoundPlayer,
      closedNoChangeSoundPlayer: generalCloseSoundPlayer,
      visiblePropertyOptions: { phetioReadOnly: true }

      // Not instrumented for PhET-iO because the list's position isn't valid until it has been popped up.
      // See https://github.com/phetsims/phet-io/issues/1102
    }, options );

    assert && assert( options.xMargin > 0 && options.yMargin > 0,
      `margins must be > 0, xMargin=${options.xMargin}, yMargin=${options.yMargin}` );

    //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
    // Pops down the list box and sets the property.value to match the chosen item.
    const fireAction = new Action( event => {

      const listItemNode = event.currentTarget;
      assert && assert( listItemNode instanceof ComboBoxListItemNode, 'expected a ComboBoxListItemNode' );

      // set value based on which item was chosen in the list box
      property.value = listItemNode.item.value;

      // hide the list
      hideListBoxCallback();

      // prevent nodes (eg, controls) behind the list from receiving the event
      event.abort();

      focusButtonCallback();
    }, {
      parameters: [ { phetioPrivate: true, valueType: SceneryEvent } ],

      // phet-io
      tandem: tandem.createTandem( 'fireAction' ),
      phetioEventType: EventType.USER
    } );

    //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
    // Handles selection from the list box.
    const selectionListener = {

      up( event ) {
        fireAction.execute( event );
      },

      // Handle keyup on each item in the list box, for a11y.
      //TODO sun#447, scenery#931 we're using keyup because keydown fires continuously
      keyup: event => {
        if ( KeyboardUtils.isAnyKeyEvent( event.domEvent, [ KeyboardUtils.KEY_ENTER, KeyboardUtils.KEY_SPACE ] ) ) {
          fireAction.execute( event );
        }
      },

      // handle activation from an assistive device that may not use a keyboard (such as mobile VoiceOver)
      click: event => {
        fireAction.execute( event );
      }
    };

    // Compute max item dimensions
    const maxItemWidth = _.maxBy( items, item => item.node.width ).node.width;
    const maxItemHeight = _.maxBy( items, item => item.node.height ).node.height;

    // Uniform dimensions for all highlighted items in the list, highlight overlaps margin by 50%
    const highlightWidth = maxItemWidth + options.xMargin;
    const highlightHeight = maxItemHeight + options.yMargin;

    // Create a node for each item in the list, and attach a listener.
    const listItemNodes = []; // {ComboBoxListItemNode[]}
    items.forEach( ( item, index ) => {

      // Create the list item node
      const listItemNode = new ComboBoxListItemNode( item, highlightWidth, highlightHeight, {
        align: options.align,
        highlightFill: options.highlightFill,
        highlightCornerRadius: options.cornerRadius,

        // highlight overlaps half of margins
        xMargin: 0.5 * options.xMargin,
        left: 0.5 * options.xMargin,
        top: ( 0.5 * options.yMargin ) + ( index * highlightHeight ),
        tandem: item.tandemName ? tandem.createTandem( item.tandemName ) : Tandem.OPTIONAL
      } );
      listItemNodes.push( listItemNode );

      listItemNode.addInputListener( selectionListener );
    } );

    const content = new VBox( {
      spacing: 0,
      excludeInvisibleChildrenFromBounds: true,
      children: listItemNodes
    } );

    // Adjust margins to account for highlight overlap
    options.xMargin = options.xMargin / 2;
    options.yMargin = options.yMargin / 2;

    super( content, options );

    // variable for tracking whether the selected value was changed by the user
    let selectionWhenListBoxOpened;

    // Make a list of sound generators for the items, using defaults if nothing was provided.
    const itemSelectedSoundPlayers = items.map( item => {
      return item.soundPlayer ?
             item.soundPlayer :
             multiSelectionSoundPlayerFactory.getSelectionSoundPlayer( items.indexOf( item ) );
    } );

    // sound generation
    this.visibleProperty.lazyLink( visible => {

      if ( visible ) {

        // play the 'opened' sound
        options.openedSoundPlayer.play();

        // keep track of what was selected when the list box was shown
        selectionWhenListBoxOpened = property.value;
      }
      else {

        // sound generation - assumes that the property value has been updated before this list box is hidden
        assert && assert( selectionWhenListBoxOpened !== undefined, 'no value for property when list box opened' );
        if ( selectionWhenListBoxOpened === property.value ) {
          options.closedNoChangeSoundPlayer.play();
        }
        else {
          const indexOfSelection = items.findIndex( item => item.value === property.value );
          itemSelectedSoundPlayers[ indexOfSelection ].play();
        }
      }
    } );

    // pdom listener for the entire list box
    this.addInputListener( {

      // When the list box gets focus, transfer focus to the ComboBoxListItemNode that matches property.value.
      focus: event => {
        if ( this.visible ) {
          this.getListItemNode( property.value ).focus();
        }
      },

      // Handle keydown
      keydown: event => {
        if ( KeyboardUtils.isAnyKeyEvent( event.domEvent, [ KeyboardUtils.KEY_ESCAPE, KeyboardUtils.KEY_TAB ] ) ) {

          // Escape and Tab hide the list box and return focus to the button
          hideListBoxCallback();
          focusButtonCallback();
        }
        else if ( KeyboardUtils.isAnyKeyEvent( event.domEvent, [ KeyboardUtils.KEY_DOWN_ARROW, KeyboardUtils.KEY_UP_ARROW ] ) ) {

          // prevent "native" behavior so that Safari doesn't make an error sound with arrow keys in
          // full screen mode, see #210
          event.domEvent.preventDefault();

          // Up/down arrow keys move the focus between items in the list box
          const direction = ( KeyboardUtils.isKeyEvent( event.domEvent, KeyboardUtils.KEY_DOWN_ARROW ) ) ? 1 : -1;
          const focusedItemIndex = this.visibleListItemNodes.indexOf( this.getFocusedItemNode() );
          assert && assert( focusedItemIndex > -1, 'how could we receive keydown without a focused list item?' );

          const nextIndex = focusedItemIndex + direction;
          this.visibleListItemNodes[ nextIndex ] && this.visibleListItemNodes[ nextIndex ].focus();

          // reserve for drag after focus has moved, as the change in focus will clear the intent on the pointer
          event.pointer.reserveForKeyboardDrag();
        }
        else if ( KeyboardUtils.isKeyEvent( event.domEvent, KeyboardUtils.KEY_HOME ) ) {
          this.visibleListItemNodes[ 0 ].focus();
        }
        else if ( KeyboardUtils.isKeyEvent( event.domEvent, KeyboardUtils.KEY_END ) ) {
          this.visibleListItemNodes[ this.visibleListItemNodes.length - 1 ].focus();
        }
      }
    } );

    // @private
    this.listItemNodes = listItemNodes;
    this.visibleListItemNodes = listItemNodes.slice();

    // @private
    this.disposeComboBoxListBox = () => {
      for ( let i = 0; i < listItemNodes; i++ ) {
        listItemNodes[ i ].dispose(); // to unregister tandem
      }
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeComboBoxListBox();
    super.dispose();
  }

  /**
   * Sets the visibility of one or more items in the listbox that correspond to a value. Assumes that each item
   * in the listbox has a unique value.
   * @param {*} value - the value associated with the ComboBoxItem
   * @param {boolean} visible
   * @public
   */
  setItemVisible( value, visible ) {
    this.getListItemNode( value ).visible = visible;
    this.visibleListItemNodes = _.filter( this.listItemNodes, itemNode => itemNode.visible );
  }

  /**
   * Is the item that corresponds to a value visible when the listbox is popped up?
   * @param {*} value
   * @returns {boolean}
   * @public
   */
  isItemVisible( value ) {
    return this.getListItemNode( value ).visible;
  }

  /**
   * Gets the ComboBoxListItemNode that corresponds to a specified value. Assumes that values are unique.
   * @param {*} value
   * @returns {ComboBoxListItemNode}
   * @private
   */
  getListItemNode( value ) {
    const listItemNode = _.find( this.listItemNodes, listItemNode => listItemNode.item.value === value );
    assert && assert( listItemNode, `no item found for value: ${value}` );
    assert && assert( listItemNode instanceof ComboBoxListItemNode, 'invalid listItemNode' );
    return listItemNode;
  }

  /**
   * Gets the item in the ComboBox that currently has focus.
   * @returns {ComboBoxListItemNode}
   * @private
   */
  getFocusedItemNode() {
    const listItemNode = _.find( this.listItemNodes, listItemNode => listItemNode.focused );
    assert && assert( listItemNode, 'no item found that has focus' );
    assert && assert( listItemNode instanceof ComboBoxListItemNode, 'invalid listItemNode' );
    return listItemNode;
  }
}

sun.register( 'ComboBoxListBox', ComboBoxListBox );
export default ComboBoxListBox;<|endoftext|>