");
+ const $poll = $(pollElem);
+
+ const pollName = $poll.data("poll-name");
+ const pollView = createPollView(helper.container, post, polls[pollName], votes[pollName]);
+
+ $poll.replaceWith($div);
+ Em.run.next(() => pollView.renderer.replaceIn(pollView, $div[0]));
+ postPollViews[pollName] = pollView;
+ });
+
+ _pollViews = postPollViews;
+ }
+
+ api.decorateCooked(createPollViews, { onlyStream: true });
+ api.cleanupStream(cleanUpPollViews);
+}
+
export default {
name: "extend-for-poll",
- initialize(container) {
-
- Post.reopen({
- // we need a proper ember object so it is bindable
- pollsChanged: function(){
- const polls = this.get("polls");
- if (polls) {
- this._polls = this._polls || {};
- _.map(polls, (v,k) => {
- const existing = this._polls[k];
- if (existing) {
- this._polls[k].setProperties(v);
- } else {
- this._polls[k] = Em.Object.create(v);
- }
- });
- this.set("pollsObject", this._polls);
- }
- }.observes("polls")
- });
-
- TopicController.reopen({
- subscribe(){
- this._super();
- this.messageBus.subscribe("/polls/" + this.get("model.id"), msg => {
- const post = this.get('model.postStream').findLoadedPost(msg.post_id);
- if (post) {
- post.set('polls', msg.polls);
- }
- });
- },
- unsubscribe(){
- this.messageBus.unsubscribe('/polls/*');
- this._super();
- }
- });
-
- // overwrite polls
- PostView.reopen({
-
- @on("postViewInserted", "postViewUpdated")
- _createPollViews($post) {
- const post = this.get("post"),
- votes = post.get("polls_votes") || {};
-
- post.pollsChanged();
- const polls = post.get("pollsObject");
-
- // don't even bother when there's no poll
- if (!polls) { return; }
-
- // TODO inject cleanly into
-
- // clean-up if needed
- this._cleanUpPollViews();
-
- const pollViews = {};
-
- // iterate over all polls
- $(".poll", $post).each(function() {
- const $div = $("
"),
- $poll = $(this),
- pollName = $poll.data("poll-name"),
- pollView = createPollView(container, post, polls[pollName], votes[pollName]);
-
- $poll.replaceWith($div);
- Em.run.next(() => pollView.renderer.replaceIn(pollView, $div[0]));
- pollViews[pollName] = pollView;
- });
-
- this.set("pollViews", pollViews);
- },
-
- @on("willClearRender")
- _cleanUpPollViews() {
- if (this.get("pollViews")) {
- _.forEach(this.get("pollViews"), v => v.destroy());
- }
- }
- });
+ initialize() {
+ withPluginApi('0.1', initializePolls);
}
};
diff --git a/test/javascripts/acceptance/composer-test.js.es6 b/test/javascripts/acceptance/composer-test.js.es6
index e8d8e8dbf59..38789579c18 100644
--- a/test/javascripts/acceptance/composer-test.js.es6
+++ b/test/javascripts/acceptance/composer-test.js.es6
@@ -192,8 +192,8 @@ test("Edit the first post", () => {
ok(!exists('.topic-post:eq(0) .post-info.edits'), 'it has no edits icon at first');
- click('.topic-post:eq(0) button[data-action=showMoreActions]');
- click('.topic-post:eq(0) button[data-action=edit]');
+ click('.topic-post:eq(0) button.show-more-actions');
+ click('.topic-post:eq(0) button.edit');
andThen(() => {
equal(find('.d-editor-input').val().indexOf('Any plans to support'), 0, 'it populates the input with the post text');
});
@@ -212,11 +212,11 @@ test("Edit the first post", () => {
test("Composer can switch between edits", () => {
visit("/t/this-is-a-test-topic/9");
- click('.topic-post:eq(0) button[data-action=edit]');
+ click('.topic-post:eq(0) button.edit');
andThen(() => {
equal(find('.d-editor-input').val().indexOf('This is the first post.'), 0, 'it populates the input with the post text');
});
- click('.topic-post:eq(1) button[data-action=edit]');
+ click('.topic-post:eq(1) button.edit');
andThen(() => {
equal(find('.d-editor-input').val().indexOf('This is the second post.'), 0, 'it populates the input with the post text');
});
@@ -225,9 +225,9 @@ test("Composer can switch between edits", () => {
test("Composer with dirty edit can toggle to another edit", () => {
visit("/t/this-is-a-test-topic/9");
- click('.topic-post:eq(0) button[data-action=edit]');
+ click('.topic-post:eq(0) button.edit');
fillIn('.d-editor-input', 'This is a dirty reply');
- click('.topic-post:eq(1) button[data-action=edit]');
+ click('.topic-post:eq(1) button.edit');
andThen(() => {
ok(exists('.bootbox.modal'), 'it pops up a confirmation dialog');
});
@@ -240,15 +240,15 @@ test("Composer with dirty edit can toggle to another edit", () => {
test("Composer can toggle between edit and reply", () => {
visit("/t/this-is-a-test-topic/9");
- click('.topic-post:eq(0) button[data-action=edit]');
+ click('.topic-post:eq(0) button.edit');
andThen(() => {
equal(find('.d-editor-input').val().indexOf('This is the first post.'), 0, 'it populates the input with the post text');
});
- click('.topic-post:eq(0) button[data-action=reply]');
+ click('.topic-post:eq(0) button.reply');
andThen(() => {
equal(find('.d-editor-input').val(), "", 'it clears the input');
});
- click('.topic-post:eq(0) button[data-action=edit]');
+ click('.topic-post:eq(0) button.edit');
andThen(() => {
equal(find('.d-editor-input').val().indexOf('This is the first post.'), 0, 'it populates the input with the post text');
});
@@ -257,9 +257,9 @@ test("Composer can toggle between edit and reply", () => {
test("Composer with dirty reply can toggle to edit", () => {
visit("/t/this-is-a-test-topic/9");
- click('.topic-post:eq(0) button[data-action=reply]');
+ click('.topic-post:eq(0) button.reply');
fillIn('.d-editor-input', 'This is a dirty reply');
- click('.topic-post:eq(0) button[data-action=edit]');
+ click('.topic-post:eq(0) button.edit');
andThen(() => {
ok(exists('.bootbox.modal'), 'it pops up a confirmation dialog');
});
@@ -272,10 +272,10 @@ test("Composer with dirty reply can toggle to edit", () => {
test("Composer draft with dirty reply can toggle to edit", () => {
visit("/t/this-is-a-test-topic/9");
- click('.topic-post:eq(0) button[data-action=reply]');
+ click('.topic-post:eq(0) button.reply');
fillIn('.d-editor-input', 'This is a dirty reply');
click('.toggler');
- click('.topic-post:eq(0) button[data-action=edit]');
+ click('.topic-post:eq(0) button.edit');
andThen(() => {
ok(exists('.bootbox.modal'), 'it pops up a confirmation dialog');
});
diff --git a/test/javascripts/components/d-editor-test.js.es6 b/test/javascripts/components/d-editor-test.js.es6
index ecb486f3a52..d8985c19c71 100644
--- a/test/javascripts/components/d-editor-test.js.es6
+++ b/test/javascripts/components/d-editor-test.js.es6
@@ -1,5 +1,5 @@
import componentTest from 'helpers/component-test';
-import { onToolbarCreate } from 'discourse/components/d-editor';
+import { withPluginApi } from 'discourse/lib/plugin-api';
moduleForComponent('d-editor', {integration: true});
@@ -540,12 +540,14 @@ componentTest('emoji', {
template: '{{d-editor value=value}}',
setup() {
// Test adding a custom button
- onToolbarCreate(toolbar => {
- toolbar.addButton({
- id: 'emoji',
- group: 'extras',
- icon: 'smile-o',
- action: 'emoji'
+ withPluginApi('0.1', api => {
+ api.onToolbarCreate(toolbar => {
+ toolbar.addButton({
+ id: 'emoji',
+ group: 'extras',
+ icon: 'smile-o',
+ action: 'emoji'
+ });
});
});
this.set('value', 'hello world.');
diff --git a/test/javascripts/components/post-menu-test.js.es6 b/test/javascripts/components/post-menu-test.js.es6
deleted file mode 100644
index a027bb660fa..00000000000
--- a/test/javascripts/components/post-menu-test.js.es6
+++ /dev/null
@@ -1,52 +0,0 @@
-import componentTest from 'helpers/component-test';
-
-moduleForComponent('post-menu', {integration: true});
-
-function setup(store) {
- const topic = store.createRecord('topic', {id: 123});
- const post = store.createRecord('post', {
- id: 1,
- post_number: 1,
- topic,
- like_count: 3,
- actions_summary: [
- {id: 2, count: 3, hidden: false, can_act: true}
- ]
- });
-
- this.on('toggleLike', function() {
- post.toggleProperty('likeAction.acted');
- });
-
- this.set('post', post);
-}
-
-componentTest('basic render', {
- template: '{{post-menu post=post}}',
- setup,
- test(assert) {
- assert.ok(!!this.$('.post-menu-area').length, 'it renders a post menu');
- assert.ok(!!this.$('.actions button[data-share-url]').length, 'it renders a share button');
- }
-});
-
-componentTest('liking', {
- template: '{{post-menu post=post toggleLike="toggleLike"}}',
- setup,
- test(assert) {
- assert.ok(!!this.$('.actions button.like').length);
- assert.ok(!!this.$('.actions button.like-count').length);
-
- click('.actions button.like');
- andThen(() => {
- assert.ok(!this.$('.actions button.like').length);
- assert.ok(!!this.$('.actions button.has-like').length);
- });
-
- click('.actions button.has-like');
- andThen(() => {
- assert.ok(!!this.$('.actions button.like').length);
- assert.ok(!this.$('.actions button.has-like').length);
- });
- }
-});
diff --git a/test/javascripts/controllers/topic-test.js.es6 b/test/javascripts/controllers/topic-test.js.es6
index 13fdc144eb8..48f0bcdbd02 100644
--- a/test/javascripts/controllers/topic-test.js.es6
+++ b/test/javascripts/controllers/topic-test.js.es6
@@ -6,6 +6,7 @@ moduleFor('controller:topic', 'controller:topic', {
});
import Topic from 'discourse/models/topic';
+import AppEvents from 'discourse/lib/app-events';
var buildTopic = function() {
return Topic.create({
@@ -62,7 +63,7 @@ test("toggledSelectedPost", function() {
});
test("selectAll", function() {
- var tc = this.subject({model: buildTopic()}),
+ var tc = this.subject({model: buildTopic(), appEvents: AppEvents.create()}),
post = Discourse.Post.create({id: 123, post_number: 2}),
postStream = tc.get('model.postStream');
diff --git a/test/javascripts/ember/resolver-test.js.es6 b/test/javascripts/ember/resolver-test.js.es6
index 948add12d8b..1e5ad1ea593 100644
--- a/test/javascripts/ember/resolver-test.js.es6
+++ b/test/javascripts/ember/resolver-test.js.es6
@@ -1,7 +1,7 @@
import DiscourseResolver from 'discourse/ember/resolver';
-var originalTemplates, originalMobileViewFlag;
-var resolver = DiscourseResolver.create();
+let originalTemplates;
+let resolver;
function lookupTemplate(name, expectedTemplate, message) {
var parseName = resolver.parseName(name);
@@ -20,13 +20,11 @@ module("lib:resolver", {
originalTemplates = Ember.TEMPLATES;
Ember.TEMPLATES = {};
- originalMobileViewFlag = Discourse.Mobile.mobileView;
- Discourse.Mobile.mobileView = false;
+ resolver = DiscourseResolver.create();
},
teardown: function() {
Ember.TEMPLATES = originalTemplates;
- Discourse.Mobile.mobileView = originalMobileViewFlag;
}
});
@@ -92,7 +90,7 @@ test("resolves mobile templates to 'mobile/' namespace", function() {
"baz"
]);
- Discourse.Mobile.mobileView = true;
+ resolver.mobileView = true;
lookupTemplate("template:foo", "mobile/foo", "finding mobile version even if normal one is not present");
lookupTemplate("template:bar", "mobile/bar", "preferring mobile version when both mobile and normal versions are present");
diff --git a/test/javascripts/fixtures/site-fixtures.js.es6 b/test/javascripts/fixtures/site-fixtures.js.es6
index 9e29ee832f9..81ce4b86975 100644
--- a/test/javascripts/fixtures/site-fixtures.js.es6
+++ b/test/javascripts/fixtures/site-fixtures.js.es6
@@ -19,7 +19,8 @@ export default {
"post_types":{
"regular":1,
"moderator_action":2,
- "small_action":3
+ "small_action":3,
+ "whisper":4
},
"group_names":[
"admins",
diff --git a/test/javascripts/helpers/component-test.js.es6 b/test/javascripts/helpers/component-test.js.es6
index 3ff28d3c94b..0fd3c8d6b2c 100644
--- a/test/javascripts/helpers/component-test.js.es6
+++ b/test/javascripts/helpers/component-test.js.es6
@@ -6,20 +6,27 @@ export default function(name, opts) {
opts = opts || {};
test(name, function(assert) {
- if (opts.setup) {
- const store = createStore();
- opts.setup.call(this, store);
- }
const appEvents = AppEvents.create();
-
- loadAllHelpers();
-
this.container.register('site-settings:main', Discourse.SiteSettings, { instantiate: false });
this.container.register('app-events:main', appEvents, { instantiate: false });
this.container.register('capabilities:main', Ember.Object);
+ this.container.register('site:main', Discourse.Site.current(), { instantiate: false });
this.container.injection('component', 'siteSettings', 'site-settings:main');
this.container.injection('component', 'appEvents', 'app-events:main');
this.container.injection('component', 'capabilities', 'capabilities:main');
+ this.container.injection('component', 'site', 'site:main');
+
+ this.siteSettings = Discourse.SiteSettings;
+
+ loadAllHelpers();
+
+ if (opts.setup) {
+ const store = createStore();
+ this.currentUser = Discourse.User.create();
+ this.container.register('store:main', store, { instantiate: false });
+ this.container.register('current-user:main', this.currentUser, { instantiate: false });
+ opts.setup.call(this, store);
+ }
andThen(() => this.render(opts.template));
andThen(() => opts.test.call(this, assert));
diff --git a/test/javascripts/helpers/create-pretender.js.es6 b/test/javascripts/helpers/create-pretender.js.es6
index fca63e935bf..0aaad1ff901 100644
--- a/test/javascripts/helpers/create-pretender.js.es6
+++ b/test/javascripts/helpers/create-pretender.js.es6
@@ -85,7 +85,23 @@ export default function() {
this.get('/users/:username/staff-info.json', () => response({}));
- this.put('/categories/:category_id', function(request) {
+ this.get('/post_action_users', () => {
+ return response({
+ post_action_users: [
+ {id: 1, username: 'eviltrout', avatar_template: '/user_avatar/default/eviltrout/{size}/1.png', username_lower: 'eviltrout' }
+ ]
+ });
+ });
+
+ this.get('/post_replies', () => {
+ return response({ post_replies: [{ id: 1234, cooked: 'wat' }] });
+ });
+
+ this.get('/post_reply_histories', () => {
+ return response({ post_reply_histories: [{ id: 1234, cooked: 'wat' }] });
+ });
+
+ this.put('/categories/:category_id', request => {
const category = parsePostData(request.requestBody);
return response({category});
});
@@ -132,6 +148,7 @@ export default function() {
this.delete('/posts/:post_id', success);
this.put('/posts/:post_id/recover', success);
+ this.get('/posts/:post_id/expand-embed', success);
this.put('/posts/:post_id', request => {
const data = parsePostData(request.requestBody);
diff --git a/test/javascripts/helpers/qunit-helpers.js.es6 b/test/javascripts/helpers/qunit-helpers.js.es6
index 44812f7ec68..c6905856612 100644
--- a/test/javascripts/helpers/qunit-helpers.js.es6
+++ b/test/javascripts/helpers/qunit-helpers.js.es6
@@ -41,7 +41,6 @@ function acceptance(name, options) {
Discourse.Utilities.avatarImg = () => "";
// For now don't do scrolling stuff in Test Mode
- Ember.CloakedCollectionView.scrolled = Ember.K;
HeaderView.reopen({examineDockHeader: Ember.K});
var siteJson = siteFixtures['site.json'].site;
diff --git a/test/javascripts/helpers/widget-test.js.es6 b/test/javascripts/helpers/widget-test.js.es6
new file mode 100644
index 00000000000..d30170ff1db
--- /dev/null
+++ b/test/javascripts/helpers/widget-test.js.es6
@@ -0,0 +1,9 @@
+import componentTest from 'helpers/component-test';
+
+export function moduleForWidget(name) {
+ moduleForComponent(name, `widget:${name}`, { integration: true });
+}
+
+export function widgetTest(name, opts) {
+ return componentTest(name, opts);
+}
diff --git a/test/javascripts/models/post-stream-test.js.es6 b/test/javascripts/models/post-stream-test.js.es6
index 41098560adf..0b9a7d3dfaa 100644
--- a/test/javascripts/models/post-stream-test.js.es6
+++ b/test/javascripts/models/post-stream-test.js.es6
@@ -27,40 +27,6 @@ test('defaults', function() {
present(postStream.get('topic'));
});
-test('daysSincePrevious when appending', function(assert) {
- const postStream = buildStream(10000001, [1,2,3]);
- const store = postStream.store;
-
- const p1 = store.createRecord('post', {id: 1, post_number: 1, created_at: "2015-05-29T18:17:35.868Z"}),
- p2 = store.createRecord('post', {id: 2, post_number: 2, created_at: "2015-06-01T01:07:25.761Z"}),
- p3 = store.createRecord('post', {id: 3, post_number: 3, created_at: "2015-06-02T01:07:25.761Z"});
-
- postStream.appendPost(p1);
- postStream.appendPost(p2);
- postStream.appendPost(p3);
-
- assert.ok(!p1.get('daysSincePrevious'));
- assert.equal(p2.get('daysSincePrevious'), 2);
- assert.equal(p3.get('daysSincePrevious'), 1);
-});
-
-test('daysSincePrevious when prepending', function(assert) {
- const postStream = buildStream(10000001, [1,2,3]);
- const store = postStream.store;
-
- const p1 = store.createRecord('post', {id: 1, post_number: 1, created_at: "2015-05-29T18:17:35.868Z"}),
- p2 = store.createRecord('post', {id: 2, post_number: 2, created_at: "2015-06-01T01:07:25.761Z"}),
- p3 = store.createRecord('post', {id: 3, post_number: 3, created_at: "2015-06-02T01:07:25.761Z"});
-
- postStream.prependPost(p3);
- postStream.prependPost(p2);
- postStream.prependPost(p1);
-
- assert.ok(!p1.get('daysSincePrevious'));
- assert.equal(p2.get('daysSincePrevious'), 2);
- assert.equal(p3.get('daysSincePrevious'), 1);
-});
-
test('appending posts', function() {
const postStream = buildStream(4567, [1, 3, 4]);
const store = postStream.store;
@@ -320,17 +286,6 @@ test("loadIntoIdentityMap with post ids", function() {
});
});
-test("loading a post's history", function() {
- const postStream = buildStream(1234);
- const store = postStream.store;
- const post = store.createRecord('post', {id: 4321});
-
- postStream.findReplyHistory(post).then(function() {
- present(postStream.findLoadedPost(2222), "it stores the returned post in the identity map");
- present(post.get('replyHistory'), "it sets the replyHistory attribute for the post");
- });
-});
-
test("staging and undoing a new post", function() {
const postStream = buildStream(10101, [1]);
const store = postStream.store;
diff --git a/test/javascripts/models/post-test.js.es6 b/test/javascripts/models/post-test.js.es6
index 77365775004..1f881df1209 100644
--- a/test/javascripts/models/post-test.js.es6
+++ b/test/javascripts/models/post-test.js.es6
@@ -14,7 +14,6 @@ test('defaults', function() {
var post = Discourse.Post.create({id: 1});
blank(post.get('deleted_at'), "it has no deleted_at by default");
blank(post.get('deleted_by'), "there is no deleted_by by default");
- equal(post.get('replyHistory.length'), 0, "there is no reply history by default");
});
test('new_user', function() {
@@ -47,16 +46,6 @@ test('updateFromPost', function() {
equal(post.get('raw'), "different raw", "raw field updated");
});
-test('hasHistory', function() {
- var post = Discourse.Post.create({id: 1});
- ok(!post.get('hasHistory'), 'posts without versions have no history');
- post.set('version', 1);
- ok(!post.get('hasHistory'), 'posts with one version have no history');
- post.set('version', 2);
- ok(post.get('hasHistory'), 'posts with more than one version have a history');
-});
-
-
test('destroy by staff', function() {
var user = Discourse.User.create({username: 'staff', staff: true}),
post = buildPost({user: user});
diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js
index 08f9256f6ff..d8ca4fe8601 100644
--- a/test/javascripts/test_helper.js
+++ b/test/javascripts/test_helper.js
@@ -111,9 +111,6 @@ QUnit.testStart(function(ctx) {
}
});
-// Don't cloak in testing
-Ember.CloakedCollectionView = Ember.CollectionView;
-
QUnit.testDone(function() {
Ember.run.debounce = origDebounce;
window.sandbox.restore();
diff --git a/test/javascripts/widgets/actions-summary-test.js.es6 b/test/javascripts/widgets/actions-summary-test.js.es6
new file mode 100644
index 00000000000..17cb172c606
--- /dev/null
+++ b/test/javascripts/widgets/actions-summary-test.js.es6
@@ -0,0 +1,80 @@
+import { moduleForWidget, widgetTest } from 'helpers/widget-test';
+
+moduleForWidget('actions-summary');
+
+widgetTest('listing actions', {
+ template: '{{mount-widget widget="actions-summary" args=args}}',
+ setup() {
+ this.set('args', {
+ actionsSummary: [
+ {action: 'off_topic', description: 'very off topic'},
+ {action: 'spam', description: 'suspicious message'}
+ ]
+ });
+ },
+ test(assert) {
+ assert.equal(this.$('.post-actions .post-action').length, 2);
+
+ click('.post-action:eq(0) .action-link a');
+ andThen(() => {
+ assert.equal(this.$('.post-action:eq(0) img.avatar').length, 1, 'clicking it shows the user');
+ });
+ }
+});
+
+widgetTest('undo', {
+ template: '{{mount-widget widget="actions-summary" args=args undoPostAction="undoPostAction"}}',
+ setup() {
+ this.set('args', {
+ actionsSummary: [
+ {action: 'off_topic', description: 'very off topic', canUndo: true},
+ ]
+ });
+
+ this.on('undoPostAction', () => this.undid = true);
+ },
+ test(assert) {
+ assert.equal(this.$('.post-actions .post-action').length, 1);
+
+ click('.action-link.undo');
+ andThen(() => {
+ assert.ok(this.undid, 'it triggered the action');
+ });
+ }
+});
+
+widgetTest('deferFlags', {
+ template: '{{mount-widget widget="actions-summary" args=args deferPostActionFlags="deferPostActionFlags"}}',
+ setup() {
+ this.set('args', {
+ actionsSummary: [
+ {action: 'off_topic', description: 'very off topic', canDeferFlags: true, count: 1},
+ ]
+ });
+
+ this.on('deferPostActionFlags', () => this.deferred = true);
+ },
+ test(assert) {
+ assert.equal(this.$('.post-actions .post-action').length, 1);
+
+ click('.action-link.defer-flags');
+ andThen(() => {
+ assert.ok(this.deferred, 'it triggered the action');
+ });
+ }
+});
+
+widgetTest('post deleted', {
+ template: '{{mount-widget widget="actions-summary" args=args}}',
+ setup() {
+ this.set('args', {
+ isDeleted: true,
+ deletedByUsername: 'eviltrout',
+ deletedByAvatarTemplate: '/images/avatar.png'
+ });
+ },
+ test(assert) {
+ assert.ok(this.$('.post-action .fa-trash-o').length === 1, 'it has the deleted icon');
+ assert.ok(this.$('.avatar[title=eviltrout]').length === 1, 'it has the deleted by avatar');
+ }
+});
diff --git a/test/javascripts/widgets/post-gutter-test.js.es6 b/test/javascripts/widgets/post-gutter-test.js.es6
new file mode 100644
index 00000000000..55c4152cb28
--- /dev/null
+++ b/test/javascripts/widgets/post-gutter-test.js.es6
@@ -0,0 +1,54 @@
+import { moduleForWidget, widgetTest } from 'helpers/widget-test';
+
+moduleForWidget('post-gutter');
+
+widgetTest("duplicate links", {
+ template: '{{mount-widget widget="post-gutter" args=args}}',
+ setup() {
+ this.set('args', {
+ links: [
+ { title: "Evil Trout Link", url: "http://eviltrout.com" },
+ { title: "Evil Trout Link", url: "http://dupe.eviltrout.com" }
+ ]
+ });
+ },
+ test(assert) {
+ assert.equal(this.$('.post-links a.track-link').length, 1, 'it hides the dupe link');
+ }
+});
+
+widgetTest("collapsed links", {
+ template: '{{mount-widget widget="post-gutter" args=args}}',
+ setup() {
+ this.set('args', {
+ links: [
+ { title: "Link 1", url: "http://eviltrout.com?1" },
+ { title: "Link 2", url: "http://eviltrout.com?2" },
+ { title: "Link 3", url: "http://eviltrout.com?3" },
+ { title: "Link 4", url: "http://eviltrout.com?4" },
+ { title: "Link 5", url: "http://eviltrout.com?5" },
+ { title: "Link 6", url: "http://eviltrout.com?6" },
+ { title: "Link 7", url: "http://eviltrout.com?7" },
+ ]
+ });
+ },
+ test(assert) {
+ assert.equal(this.$('.post-links a.track-link').length, 5, 'collapses by default');
+ click('a.toggle-more');
+ andThen(() => {
+ assert.equal(this.$('.post-links a.track-link').length, 7);
+ });
+ }
+});
+
+widgetTest("reply as new topic", {
+ template: '{{mount-widget widget="post-gutter" args=args newTopicAction="newTopicAction"}}',
+ setup() {
+ this.set('args', { canReplyAsNewTopic: true });
+ this.on('newTopicAction', () => this.newTopicTriggered = true);
+ },
+ test(assert) {
+ click('a.reply-new');
+ andThen(() => assert.ok(this.newTopicTriggered));
+ }
+});
diff --git a/test/javascripts/widgets/post-stream-test.js.es6 b/test/javascripts/widgets/post-stream-test.js.es6
new file mode 100644
index 00000000000..396ae206c0e
--- /dev/null
+++ b/test/javascripts/widgets/post-stream-test.js.es6
@@ -0,0 +1,66 @@
+import { moduleForWidget, widgetTest } from 'helpers/widget-test';
+import Topic from 'discourse/models/topic';
+import Post from 'discourse/models/post';
+
+moduleForWidget('post-stream');
+
+function postStreamTest(name, attrs) {
+ widgetTest(name, {
+ template: `{{mount-widget widget="post-stream" args=(as-hash posts=posts)}}`,
+ setup() {
+ this.set('posts', attrs.posts.call(this));
+ },
+ test: attrs.test
+ });
+}
+
+postStreamTest('basics', {
+ posts() {
+ const site = this.container.lookup('site:main');
+ const topic = Topic.create({ details: { created_by: { id: 123 } } });
+ return [
+ Post.create({ topic, id: 1, post_number: 1, user_id: 123, primary_group_name: 'trout',
+ avatar_template: '/images/avatar.png' }),
+ Post.create({ topic, id: 2, post_number: 2, post_type: site.get('post_types.moderator_action') }),
+ Post.create({ topic, id: 3, post_number: 3, hidden: true }),
+ Post.create({ topic, id: 4, post_number: 4, post_type: site.get('post_types.whisper') }),
+ Post.create({ topic, id: 5, post_number: 5, wiki: true, via_email: true })
+ ];
+ },
+
+ test(assert) {
+ assert.equal(this.$('.post-stream').length, 1);
+ assert.equal(this.$('.topic-post').length, 5, 'renders all posts');
+
+ // look for special class bindings
+ assert.equal(this.$('.topic-post:eq(0).topic-owner').length, 1, 'it applies the topic owner class');
+ assert.equal(this.$('.topic-post:eq(0).group-trout').length, 1, 'it applies the primary group class');
+ assert.equal(this.$('.topic-post:eq(0).regular').length, 1, 'it applies the regular class');
+ assert.equal(this.$('.topic-post:eq(1).moderator').length, 1, 'it applies the moderator class');
+ assert.equal(this.$('.topic-post:eq(2).post-hidden').length, 1, 'it applies the hidden class');
+ assert.equal(this.$('.topic-post:eq(3).whisper').length, 1, 'it applies the whisper class');
+ assert.equal(this.$('.topic-post:eq(4).wiki').length, 1, 'it applies the wiki class');
+
+ // it renders an article for the body with appropriate attributes
+ assert.equal(this.$('article#post_2').length, 1);
+ assert.equal(this.$('article[data-user-id=123]').length, 1);
+ assert.equal(this.$('article[data-post-id=3]').length, 1);
+ assert.equal(this.$('article#post_5.via-email').length, 1);
+
+ assert.equal(this.$('article:eq(0) .main-avatar').length, 1, 'renders the main avatar');
+ }
+});
+
+postStreamTest('deleted posts', {
+ posts() {
+ const topic = Topic.create({ details: { created_by: { id: 123 } } });
+ return [
+ Post.create({ topic, id: 1, post_number: 1, deleted_at: new Date().toString() }),
+ ];
+ },
+
+ test(assert) {
+ assert.equal(this.$('.topic-post.deleted').length, 1, 'it applies the deleted class');
+ assert.equal(this.$('.deleted-user-avatar').length, 1, 'it has the trash avatar');
+ }
+});
diff --git a/test/javascripts/widgets/post-test.js.es6 b/test/javascripts/widgets/post-test.js.es6
new file mode 100644
index 00000000000..5c853c0dd65
--- /dev/null
+++ b/test/javascripts/widgets/post-test.js.es6
@@ -0,0 +1,784 @@
+import { moduleForWidget, widgetTest } from 'helpers/widget-test';
+
+moduleForWidget('post');
+
+widgetTest('basic elements', {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { shareUrl: '/example', post_number: 1 });
+ },
+ test(assert) {
+ assert.ok(this.$('.names').length, 'includes poster name');
+
+ assert.ok(this.$('a.post-date').length, 'includes post date');
+ assert.ok(this.$('a.post-date[data-share-url]').length);
+ assert.ok(this.$('a.post-date[data-post-number]').length);
+ }
+});
+
+widgetTest('wiki', {
+ template: '{{mount-widget widget="post" args=args editPost="editPost"}}',
+ setup() {
+ this.set('args', { wiki: true });
+ this.on('editPost', () => this.editPostCalled = true);
+ },
+ test(assert) {
+ click('.post-info.wiki');
+ andThen(() => {
+ assert.ok(this.editPostCalled, 'clicking the wiki icon edits the post');
+ });
+ }
+});
+
+widgetTest('via-email', {
+ template: '{{mount-widget widget="post" args=args showRawEmail="showRawEmail"}}',
+ setup() {
+ this.set('args', { via_email: true, canViewRawEmail: true });
+ this.on('showRawEmail', () => this.rawEmailShown = true);
+ },
+ test(assert) {
+ click('.post-info.via-email');
+ andThen(() => {
+ assert.ok(this.rawEmailShown, 'clicking the enveloppe shows the raw email');
+ });
+ }
+});
+
+widgetTest('via-email without permission', {
+ template: '{{mount-widget widget="post" args=args showRawEmail="showRawEmail"}}',
+ setup() {
+ this.set('args', { via_email: true, canViewRawEmail: false });
+ this.on('showRawEmail', () => this.rawEmailShown = true);
+ },
+ test(assert) {
+ click('.post-info.via-email');
+ andThen(() => {
+ assert.ok(!this.rawEmailShown, `clicking the enveloppe doesn't show the raw email`);
+ });
+ }
+});
+
+widgetTest('history', {
+ template: '{{mount-widget widget="post" args=args showHistory="showHistory"}}',
+ setup() {
+ this.set('args', { version: 3, canViewEditHistory: true });
+ this.on('showHistory', () => this.historyShown = true);
+ },
+ test(assert) {
+ click('.post-info.edits');
+ andThen(() => {
+ assert.ok(this.historyShown, 'clicking the pencil shows the history');
+ });
+ }
+});
+
+widgetTest('history without view permission', {
+ template: '{{mount-widget widget="post" args=args showHistory="showHistory"}}',
+ setup() {
+ this.set('args', { version: 3, canViewEditHistory: false });
+ this.on('showHistory', () => this.historyShown = true);
+ },
+ test(assert) {
+ click('.post-info.edits');
+ andThen(() => {
+ assert.ok(!this.historyShown, `clicking the pencil doesn't show the history`);
+ });
+ }
+});
+
+widgetTest('whisper', {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { isWhisper: true });
+ },
+ test(assert) {
+ assert.ok(this.$('.topic-post.whisper').length === 1);
+ assert.ok(this.$('.post-info.whisper').length === 1);
+ }
+});
+
+widgetTest('like count button', {
+ template: '{{mount-widget widget="post" model=post args=args}}',
+ setup(store) {
+ const topic = store.createRecord('topic', {id: 123});
+ const post = store.createRecord('post', {
+ id: 1,
+ post_number: 1,
+ topic,
+ like_count: 3,
+ actions_summary: [ {id: 2, count: 1, hidden: false, can_act: true} ]
+ });
+ this.set('post', post);
+ this.set('args', { likeCount: 1 });
+ },
+ test(assert) {
+ assert.ok(this.$('button.like-count').length === 1);
+ assert.ok(this.$('.who-liked').length === 0);
+
+ // toggle it on
+ click('button.like-count');
+ andThen(() => {
+ assert.ok(this.$('.who-liked').length === 1);
+ assert.ok(this.$('.who-liked a.trigger-user-card').length === 1);
+ });
+
+ // toggle it off
+ click('button.like-count');
+ andThen(() => {
+ assert.ok(this.$('.who-liked').length === 0);
+ assert.ok(this.$('.who-liked a.trigger-user-card').length === 0);
+ });
+ }
+});
+
+widgetTest(`like count with no likes`, {
+ template: '{{mount-widget widget="post" model=post args=args}}',
+ setup() {
+ this.set('args', { likeCount: 0 });
+ },
+ test(assert) {
+ assert.ok(this.$('button.like-count').length === 0);
+ }
+});
+
+widgetTest('share button', {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { shareUrl: 'http://share-me.example.com' });
+ },
+ test(assert) {
+ assert.ok(!!this.$('.actions button[data-share-url]').length, 'it renders a share button');
+ }
+});
+
+widgetTest('liking', {
+ template: '{{mount-widget widget="post-menu" args=args toggleLike="toggleLike"}}',
+ setup() {
+ const args = { showLike: true, canToggleLike: true };
+ this.set('args', args);
+ this.on('toggleLike', () => {
+ args.liked = !args.liked;
+ args.likeCount = args.liked ? 1 : 0;
+ });
+ },
+ test(assert) {
+ assert.ok(!!this.$('.actions button.like').length);
+ assert.ok(this.$('.actions button.like-count').length === 0);
+
+ click('.actions button.like');
+ andThen(() => {
+ assert.ok(!this.$('.actions button.like').length);
+ assert.ok(!!this.$('.actions button.has-like').length);
+ assert.ok(this.$('.actions button.like-count').length === 1);
+ });
+
+ click('.actions button.has-like');
+ andThen(() => {
+ assert.ok(!!this.$('.actions button.like').length);
+ assert.ok(!this.$('.actions button.has-like').length);
+ assert.ok(this.$('.actions button.like-count').length === 0);
+ });
+ }
+});
+
+widgetTest('edit button', {
+ template: '{{mount-widget widget="post" args=args editPost="editPost"}}',
+ setup() {
+ this.set('args', { canEdit: true });
+ this.on('editPost', () => this.editPostCalled = true);
+ },
+ test(assert) {
+ click('button.edit');
+ andThen(() => {
+ assert.ok(this.editPostCalled, 'it triggered the edit action');
+ });
+ }
+});
+
+widgetTest(`edit button - can't edit`, {
+ template: '{{mount-widget widget="post" args=args editPost="editPost"}}',
+ setup() {
+ this.set('args', { canEdit: false });
+ },
+ test(assert) {
+ assert.equal(this.$('button.edit').length, 0, `button is not displayed`);
+ }
+});
+
+widgetTest('recover button', {
+ template: '{{mount-widget widget="post" args=args deletePost="deletePost"}}',
+ setup() {
+ this.set('args', { canDelete: true });
+ this.on('deletePost', () => this.deletePostCalled = true);
+ },
+ test(assert) {
+ click('button.delete');
+ andThen(() => {
+ assert.ok(this.deletePostCalled, 'it triggered the delete action');
+ });
+ }
+});
+
+widgetTest('delete topic button', {
+ template: '{{mount-widget widget="post" args=args deletePost="deletePost"}}',
+ setup() {
+ this.set('args', { canDeleteTopic: true });
+ this.on('deletePost', () => this.deletePostCalled = true);
+ },
+ test(assert) {
+ click('button.delete');
+ andThen(() => {
+ assert.ok(this.deletePostCalled, 'it triggered the delete action');
+ });
+ }
+});
+
+widgetTest(`delete topic button - can't delete`, {
+ template: '{{mount-widget widget="post" args=args deletePost="deletePost"}}',
+ setup() {
+ this.set('args', { canDeleteTopic: false });
+ },
+ test(assert) {
+ assert.equal(this.$('button.delete').length, 0, `button is not displayed`);
+ }
+});
+
+widgetTest('recover topic button', {
+ template: '{{mount-widget widget="post" args=args recoverPost="recoverPost"}}',
+ setup() {
+ this.set('args', { canRecoverTopic: true });
+ this.on('recoverPost', () => this.recovered = true);
+ },
+ test(assert) {
+ click('button.recover');
+ andThen(() => assert.ok(this.recovered));
+ }
+});
+
+widgetTest(`recover topic button - can't recover`, {
+ template: '{{mount-widget widget="post" args=args deletePost="deletePost"}}',
+ setup() {
+ this.set('args', { canRecoverTopic: false });
+ },
+ test(assert) {
+ assert.equal(this.$('button.recover').length, 0, `button is not displayed`);
+ }
+});
+
+widgetTest('delete post button', {
+ template: '{{mount-widget widget="post" args=args deletePost="deletePost"}}',
+ setup() {
+ this.set('args', { canDelete: true });
+ this.on('deletePost', () => this.deletePostCalled = true);
+ },
+ test(assert) {
+ click('button.delete');
+ andThen(() => {
+ assert.ok(this.deletePostCalled, 'it triggered the delete action');
+ });
+ }
+});
+
+widgetTest(`delete post button - can't delete`, {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { canDelete: false });
+ },
+ test(assert) {
+ assert.equal(this.$('button.delete').length, 0, `button is not displayed`);
+ }
+});
+
+widgetTest('recover post button', {
+ template: '{{mount-widget widget="post" args=args recoverPost="recoverPost"}}',
+ setup() {
+ this.set('args', { canRecover: true });
+ this.on('recoverPost', () => this.recovered = true);
+ },
+ test(assert) {
+ click('button.recover');
+ andThen(() => assert.ok(this.recovered));
+ }
+});
+
+widgetTest(`recover post button - can't recover`, {
+ template: '{{mount-widget widget="post" args=args deletePost="deletePost"}}',
+ setup() {
+ this.set('args', { canRecover: false });
+ },
+ test(assert) {
+ assert.equal(this.$('button.recover').length, 0, `button is not displayed`);
+ }
+});
+
+widgetTest(`flagging`, {
+ template: '{{mount-widget widget="post" args=args showFlags="showFlags"}}',
+ setup() {
+ this.set('args', { canFlag: true });
+ this.on('showFlags', () => this.flagsShown = true);
+ },
+ test(assert) {
+ assert.ok(this.$('button.create-flag').length === 1);
+
+ click('button.create-flag');
+ andThen(() => {
+ assert.ok(this.flagsShown, 'it triggered the action');
+ });
+ }
+});
+
+widgetTest(`flagging: can't flag`, {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { canFlag: false });
+ },
+ test(assert) {
+ assert.ok(this.$('button.create-flag').length === 0);
+ }
+});
+
+widgetTest(`read indicator`, {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { read: true });
+ },
+ test(assert) {
+ assert.ok(this.$('.read-state.read').length);
+ }
+});
+
+widgetTest(`unread indicator`, {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { read: false });
+ },
+ test(assert) {
+ assert.ok(this.$('.read-state').length);
+ }
+});
+
+widgetTest("reply directly above (supressed)", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', {
+ replyToUsername: 'eviltrout',
+ replyToAvatarTemplate: '/images/avatar.png',
+ replyDirectlyAbove: true
+ });
+ },
+ test(assert) {
+ assert.equal(this.$('a.reply-to-tab').length, 0, 'hides the tab');
+ assert.equal(this.$('.avoid-tab').length, 0, "doesn't have the avoid tab class");
+ }
+});
+
+widgetTest("reply a few posts above (supressed)", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', {
+ replyToUsername: 'eviltrout',
+ replyToAvatarTemplate: '/images/avatar.png',
+ replyDirectlyAbove: false
+ });
+ },
+ test(assert) {
+ assert.ok(this.$('a.reply-to-tab').length, 'shows the tab');
+ assert.equal(this.$('.avoid-tab').length, 1, "has the avoid tab class");
+ }
+});
+
+widgetTest("reply directly above", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', {
+ replyToUsername: 'eviltrout',
+ replyToAvatarTemplate: '/images/avatar.png',
+ replyDirectlyAbove: true
+ });
+ this.siteSettings.suppress_reply_directly_above = false;
+ },
+ test(assert) {
+ assert.equal(this.$('.avoid-tab').length, 1, "has the avoid tab class");
+ click('a.reply-to-tab');
+ andThen(() => {
+ assert.equal(this.$('section.embedded-posts.top .cooked').length, 1);
+ assert.equal(this.$('section.embedded-posts i.fa-arrow-up').length, 1);
+ });
+ }
+});
+
+widgetTest("cooked content hidden", {
+ template: '{{mount-widget widget="post" args=args expandHidden="expandHidden"}}',
+ setup() {
+ this.set('args', { cooked_hidden: true });
+ this.on('expandHidden', () => this.unhidden = true);
+ },
+ test(assert) {
+ click('.topic-body .expand-hidden');
+ andThen(() => {
+ assert.ok(this.unhidden, 'triggers the action');
+ });
+ }
+});
+
+widgetTest("expand first post", {
+ template: '{{mount-widget widget="post" model=post args=args}}',
+ setup(store) {
+ this.set('args', { expandablePost: true });
+ this.set('post', store.createRecord('post', { id: 1234 }));
+ },
+ test(assert) {
+ click('.topic-body .expand-post');
+ andThen(() => {
+ assert.equal(this.$('.expand-post').length, 0, 'button is gone');
+ });
+ }
+});
+
+widgetTest("can't bookmark", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { canBookmark: false });
+ },
+ test(assert) {
+ assert.equal(this.$('button.bookmark').length, 0);
+ assert.equal(this.$('button.bookmarked').length, 0);
+ }
+});
+
+widgetTest("bookmark", {
+ template: '{{mount-widget widget="post" args=args toggleBookmark="toggleBookmark"}}',
+ setup() {
+ const args = { canBookmark: true };
+
+ this.set('args', args);
+ this.on('toggleBookmark', () => args.bookmarked = true);
+ },
+ test(assert) {
+ assert.equal(this.$('.post-menu-area .bookmark').length, 1);
+ assert.equal(this.$('button.bookmarked').length, 0);
+
+ click('button.bookmark');
+ andThen(() => {
+ assert.equal(this.$('button.bookmarked').length, 1);
+ });
+ }
+});
+
+widgetTest("can't show admin menu when you can't manage", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { canManage: false });
+ },
+ test(assert) {
+ assert.equal(this.$('.post-menu-area .show-post-admin-menu').length, 0);
+ }
+});
+
+widgetTest("show admin menu", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { canManage: true });
+ },
+ test(assert) {
+ assert.equal(this.$('.post-admin-menu').length, 0);
+ click('.post-menu-area .show-post-admin-menu');
+ andThen(() => {
+ assert.equal(this.$('.post-admin-menu').length, 1, 'it shows the popup');
+ });
+ click('.post-menu-area');
+ andThen(() => {
+ assert.equal(this.$('.post-admin-menu').length, 0, 'clicking outside clears the popup');
+ });
+ }
+});
+
+widgetTest("toggle moderator post", {
+ template: '{{mount-widget widget="post" args=args togglePostType="togglePostType"}}',
+ setup() {
+ this.set('args', { canManage: true });
+ this.on('togglePostType', () => this.toggled = true);
+ },
+ test(assert) {
+ click('.post-menu-area .show-post-admin-menu');
+ click('.post-admin-menu .toggle-post-type');
+ andThen(() => {
+ assert.ok(this.toggled);
+ assert.equal(this.$('.post-admin-menu').length, 0, 'also hides the menu');
+ });
+ }
+});
+widgetTest("toggle moderator post", {
+ template: '{{mount-widget widget="post" args=args togglePostType="togglePostType"}}',
+ setup() {
+ this.set('args', { canManage: true });
+ this.on('togglePostType', () => this.toggled = true);
+ },
+ test(assert) {
+ click('.post-menu-area .show-post-admin-menu');
+ click('.post-admin-menu .toggle-post-type');
+ andThen(() => {
+ assert.ok(this.toggled);
+ assert.equal(this.$('.post-admin-menu').length, 0, 'also hides the menu');
+ });
+ }
+});
+
+widgetTest("rebake post", {
+ template: '{{mount-widget widget="post" args=args rebakePost="rebakePost"}}',
+ setup() {
+ this.set('args', { canManage: true });
+ this.on('rebakePost', () => this.baked = true);
+ },
+ test(assert) {
+ click('.post-menu-area .show-post-admin-menu');
+ click('.post-admin-menu .rebuild-html');
+ andThen(() => {
+ assert.ok(this.baked);
+ assert.equal(this.$('.post-admin-menu').length, 0, 'also hides the menu');
+ });
+ }
+});
+
+widgetTest("unhide post", {
+ template: '{{mount-widget widget="post" args=args unhidePost="unhidePost"}}',
+ setup() {
+ this.set('args', { canManage: true, hidden: true });
+ this.on('unhidePost', () => this.unhidden = true);
+ },
+ test(assert) {
+ click('.post-menu-area .show-post-admin-menu');
+ click('.post-admin-menu .unhide-post');
+ andThen(() => {
+ assert.ok(this.unhidden);
+ assert.equal(this.$('.post-admin-menu').length, 0, 'also hides the menu');
+ });
+ }
+});
+
+widgetTest("change owner", {
+ template: '{{mount-widget widget="post" args=args changePostOwner="changePostOwner"}}',
+ setup() {
+ this.currentUser.admin = true;
+ this.set('args', { canManage: true });
+ this.on('changePostOwner', () => this.owned = true);
+ },
+ test(assert) {
+ click('.post-menu-area .show-post-admin-menu');
+ click('.post-admin-menu .change-owner');
+ andThen(() => {
+ assert.ok(this.owned);
+ assert.equal(this.$('.post-admin-menu').length, 0, 'also hides the menu');
+ });
+ }
+});
+
+widgetTest("reply", {
+ template: '{{mount-widget widget="post" args=args replyToPost="replyToPost"}}',
+ setup() {
+ this.set('args', { canCreatePost: true });
+ this.on('replyToPost', () => this.replied = true);
+ },
+ test(assert) {
+ click('.post-controls .create');
+ andThen(() => {
+ assert.ok(this.replied);
+ });
+ }
+});
+
+widgetTest("reply - without permissions", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { canCreatePost: false });
+ },
+ test(assert) {
+ assert.equal(this.$('.post-controls .create').length, 0);
+ }
+});
+
+widgetTest("replies - no replies", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', {replyCount: 0});
+ },
+ test(assert) {
+ assert.equal(this.$('button.show-replies').length, 0);
+ }
+});
+
+widgetTest("replies - multiple replies", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.siteSettings.suppress_reply_directly_below = true;
+ this.set('args', {replyCount: 2, replyDirectlyBelow: true});
+ },
+ test(assert) {
+ assert.equal(this.$('button.show-replies').length, 1);
+ }
+});
+
+widgetTest("replies - one below, suppressed", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.siteSettings.suppress_reply_directly_below = true;
+ this.set('args', {replyCount: 1, replyDirectlyBelow: true});
+ },
+ test(assert) {
+ assert.equal(this.$('button.show-replies').length, 0);
+ }
+});
+
+widgetTest("replies - one below, not suppressed", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.siteSettings.suppress_reply_directly_below = false;
+ this.set('args', {id: 6654, replyCount: 1, replyDirectlyBelow: true});
+ },
+ test(assert) {
+ click('button.show-replies');
+ andThen(() => {
+ assert.equal(this.$('section.embedded-posts.bottom .cooked').length, 1);
+ assert.equal(this.$('section.embedded-posts i.fa-arrow-down').length, 1);
+ });
+ }
+});
+
+widgetTest("topic map not shown", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { showTopicMap: false });
+ },
+ test(assert) {
+ assert.equal(this.$('.topic-map').length, 0);
+ }
+});
+
+widgetTest("topic map - few posts", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', {
+ showTopicMap: true,
+ topicPostsCount: 2,
+ participants: [
+ {username: 'eviltrout'},
+ {username: 'codinghorror'},
+ ]
+ });
+ },
+ test(assert) {
+ assert.equal(this.$('li.avatars a.poster').length, 0, 'shows no participants when collapsed');
+
+ click('nav.buttons button');
+ andThen(() => {
+ assert.equal(this.$('.topic-map-expanded a.poster').length, 2, 'shows all when expanded');
+ });
+ }
+});
+
+widgetTest("topic map - participants", {
+ template: '{{mount-widget widget="post" args=args toggleParticipant="toggleParticipant"}}',
+ setup() {
+ this.set('args', {
+ showTopicMap: true,
+ topicPostsCount: 10,
+ participants: [
+ {username: 'eviltrout'},
+ {username: 'codinghorror'},
+ {username: 'sam'},
+ {username: 'ZogStrIP'},
+ ],
+ userFilters: ['sam', 'codinghorror']
+ });
+
+ this.on('toggleParticipant', () => this.participantToggled = true);
+ },
+ test(assert) {
+ assert.equal(this.$('li.avatars a.poster').length, 3, 'limits to three participants');
+
+ click('nav.buttons button');
+ andThen(() => {
+ assert.equal(this.$('li.avatars a.poster').length, 0);
+ assert.equal(this.$('.topic-map-expanded a.poster').length, 4, 'shows all when expanded');
+ assert.equal(this.$('a.poster.toggled').length, 2, 'two are toggled');
+ });
+
+ click('.topic-map-expanded a.poster:eq(0)');
+ andThen(() => assert.ok(this.participantToggled));
+ }
+});
+
+widgetTest("topic map - links", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', {
+ showTopicMap: true,
+ topicLinks: [
+ {url: 'http://link1.example.com', clicks: 0},
+ {url: 'http://link2.example.com', clicks: 0},
+ {url: 'http://link3.example.com', clicks: 0},
+ {url: 'http://link4.example.com', clicks: 0},
+ {url: 'http://link5.example.com', clicks: 0},
+ {url: 'http://link6.example.com', clicks: 0},
+ ]
+ });
+ },
+ test(assert) {
+ assert.equal(this.$('.topic-map').length, 1);
+ assert.equal(this.$('.map.map-collapsed').length, 1);
+ assert.equal(this.$('.topic-map-expanded').length, 0);
+
+ click('nav.buttons button');
+ andThen(() => {
+ assert.equal(this.$('.map.map-collapsed').length, 0);
+ assert.equal(this.$('.topic-map i.fa-chevron-up').length, 1);
+ assert.equal(this.$('.topic-map-expanded').length, 1);
+ assert.equal(this.$('.topic-map-expanded .topic-link').length, 5, 'it limits the links displayed');
+ });
+
+ click('.link-summary a');
+ andThen(() => {
+ assert.equal(this.$('.topic-map-expanded .topic-link').length, 6, 'all links now shown');
+ });
+ }
+});
+
+widgetTest("topic map - no summary", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', { showTopicMap: true });
+ },
+ test(assert) {
+ assert.equal(this.$('.toggle-summary').length, 0);
+ }
+});
+
+widgetTest("topic map - has summary", {
+ template: '{{mount-widget widget="post" args=args toggleSummary="toggleSummary"}}',
+ setup() {
+ this.set('args', { showTopicMap: true, hasTopicSummary: true });
+ this.on('toggleSummary', () => this.summaryToggled = true);
+ },
+ test(assert) {
+ assert.equal(this.$('.toggle-summary').length, 1);
+
+ click('.toggle-summary button');
+ andThen(() => assert.ok(this.summaryToggled));
+ }
+});
+
+widgetTest("pm map", {
+ template: '{{mount-widget widget="post" args=args}}',
+ setup() {
+ this.set('args', {
+ showTopicMap: true,
+ showPMMap: true,
+ allowedGroups: [],
+ allowedUsers: [ Ember.Object.create({ username: 'eviltrout' }) ]
+ });
+ },
+ test(assert) {
+ assert.equal(this.$('.private-message-map').length, 1);
+ assert.equal(this.$('.private-message-map .user').length, 1);
+ }
+});
diff --git a/test/javascripts/widgets/poster-name-test.js.es6 b/test/javascripts/widgets/poster-name-test.js.es6
new file mode 100644
index 00000000000..5e6782625a2
--- /dev/null
+++ b/test/javascripts/widgets/poster-name-test.js.es6
@@ -0,0 +1,67 @@
+import { moduleForWidget, widgetTest } from 'helpers/widget-test';
+
+moduleForWidget('poster-name');
+
+widgetTest('basic rendering', {
+ template: '{{mount-widget widget="poster-name" args=args}}',
+ setup() {
+ this.set('args', {
+ username: 'eviltrout',
+ usernameUrl: '/users/eviltrout',
+ name: 'Robin Ward',
+ user_title: 'Trout Master' });
+ },
+ test(assert) {
+ assert.ok(this.$('.names').length);
+ assert.ok(this.$('span.username').length);
+ assert.ok(this.$('a[data-auto-route=true]').length);
+ assert.ok(this.$('a[data-user-card=eviltrout]').length);
+ assert.equal(this.$('.username a').text(), 'eviltrout');
+ assert.equal(this.$('.full-name a').text(), 'Robin Ward');
+ assert.equal(this.$('.user-title').text(), 'Trout Master');
+ }
+});
+
+widgetTest('extra classes and glyphs', {
+ template: '{{mount-widget widget="poster-name" args=args}}',
+ setup() {
+ this.set('args', {
+ username: 'eviltrout',
+ usernameUrl: '/users/eviltrout',
+ staff: true,
+ admin: true,
+ moderator: true,
+ new_user: true,
+ primary_group_name: 'fish'
+ });
+ },
+ test(assert) {
+ assert.ok(this.$('span.staff').length);
+ assert.ok(this.$('span.admin').length);
+ assert.ok(this.$('span.moderator').length);
+ assert.ok(this.$('i.fa-shield').length);
+ assert.ok(this.$('span.new-user').length);
+ assert.ok(this.$('span.fish').length);
+ }
+});
+
+widgetTest('disable display name on posts', {
+ template: '{{mount-widget widget="poster-name" args=args}}',
+ setup() {
+ this.siteSettings.display_name_on_posts = false;
+ this.set('args', { username: 'eviltrout', name: 'Robin Ward' });
+ },
+ test(assert) {
+ assert.equal(this.$('.full-name').length, 0);
+ }
+});
+
+widgetTest("doesn't render a name if it's similar to the username", {
+ template: '{{mount-widget widget="poster-name" args=args}}',
+ setup() {
+ this.set('args', { username: 'eviltrout', name: 'evil-trout' });
+ },
+ test(assert) {
+ assert.equal(this.$('.full-name').length, 0);
+ }
+});
diff --git a/app/assets/javascripts/discourse/lib/highlight.js b/vendor/assets/javascripts/highlight.js
similarity index 100%
rename from app/assets/javascripts/discourse/lib/highlight.js
rename to vendor/assets/javascripts/highlight.js
diff --git a/vendor/assets/javascripts/jquery.debug.js b/vendor/assets/javascripts/jquery.debug.js
index 79d631ff463..1e0ba997403 100644
--- a/vendor/assets/javascripts/jquery.debug.js
+++ b/vendor/assets/javascripts/jquery.debug.js
@@ -1,15 +1,15 @@
/*!
- * jQuery JavaScript Library v2.1.3
+ * jQuery JavaScript Library v2.2.0
* http://jquery.com/
*
* Includes Sizzle.js
* http://sizzlejs.com/
*
- * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
+ * Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
- * Date: 2014-12-18T15:11Z
+ * Date: 2016-01-08T20:02Z
*/
(function( global, factory ) {
@@ -41,10 +41,11 @@
// Can't be in strict mode, several libs including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
-//
-
+//"use strict";
var arr = [];
+var document = window.document;
+
var slice = arr.slice;
var concat = arr.concat;
@@ -64,13 +65,11 @@ var support = {};
var
- // Use the correct document accordingly with window argument (sandbox)
- document = window.document,
-
- version = "2.1.3",
+ version = "2.2.0",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
+
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
@@ -90,6 +89,7 @@ var
};
jQuery.fn = jQuery.prototype = {
+
// The current version of jQuery being used
jquery: version,
@@ -133,16 +133,14 @@ jQuery.fn = jQuery.prototype = {
},
// Execute a callback for every element in the matched set.
- // (You can seed the arguments with an array of args, but this is
- // only used internally.)
- each: function( callback, args ) {
- return jQuery.each( this, callback, args );
+ each: function( callback ) {
+ return jQuery.each( this, callback );
},
map: function( callback ) {
- return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return this.pushStack( jQuery.map( this, function( elem, i ) {
return callback.call( elem, i, elem );
- }));
+ } ) );
},
slice: function() {
@@ -160,11 +158,11 @@ jQuery.fn = jQuery.prototype = {
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
- return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
},
end: function() {
- return this.prevObject || this.constructor(null);
+ return this.prevObject || this.constructor();
},
// For internal use only.
@@ -176,7 +174,7 @@ jQuery.fn = jQuery.prototype = {
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
- target = arguments[0] || {},
+ target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
@@ -191,7 +189,7 @@ jQuery.extend = jQuery.fn.extend = function() {
}
// Handle case when target is a string or something (possible in deep copy)
- if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
@@ -202,8 +200,10 @@ jQuery.extend = jQuery.fn.extend = function() {
}
for ( ; i < length; i++ ) {
+
// Only deal with non-null/undefined values
- if ( (options = arguments[ i ]) != null ) {
+ if ( ( options = arguments[ i ] ) != null ) {
+
// Extend the base object
for ( name in options ) {
src = target[ name ];
@@ -215,13 +215,15 @@ jQuery.extend = jQuery.fn.extend = function() {
}
// Recurse if we're merging plain objects or arrays
- if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+ ( copyIsArray = jQuery.isArray( copy ) ) ) ) {
+
if ( copyIsArray ) {
copyIsArray = false;
- clone = src && jQuery.isArray(src) ? src : [];
+ clone = src && jQuery.isArray( src ) ? src : [];
} else {
- clone = src && jQuery.isPlainObject(src) ? src : {};
+ clone = src && jQuery.isPlainObject( src ) ? src : {};
}
// Never move original objects, clone them
@@ -239,7 +241,8 @@ jQuery.extend = jQuery.fn.extend = function() {
return target;
};
-jQuery.extend({
+jQuery.extend( {
+
// Unique for each copy of jQuery on the page
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
@@ -253,7 +256,7 @@ jQuery.extend({
noop: function() {},
isFunction: function( obj ) {
- return jQuery.type(obj) === "function";
+ return jQuery.type( obj ) === "function";
},
isArray: Array.isArray,
@@ -263,14 +266,17 @@ jQuery.extend({
},
isNumeric: function( obj ) {
+
// parseFloat NaNs numeric-cast false positives (null|true|false|"")
// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
// subtraction forces infinities to NaN
// adding 1 corrects loss of precision from parseFloat (#15100)
- return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
+ var realStringObj = obj && obj.toString();
+ return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
},
isPlainObject: function( obj ) {
+
// Not plain objects:
// - Any object or value whose internal [[Class]] property is not "[object Object]"
// - DOM nodes
@@ -301,9 +307,10 @@ jQuery.extend({
if ( obj == null ) {
return obj + "";
}
+
// Support: Android<4.0, iOS<6 (functionish RegExp)
return typeof obj === "object" || typeof obj === "function" ?
- class2type[ toString.call(obj) ] || "object" :
+ class2type[ toString.call( obj ) ] || "object" :
typeof obj;
},
@@ -315,16 +322,19 @@ jQuery.extend({
code = jQuery.trim( code );
if ( code ) {
+
// If the code includes a valid, prologue position
// strict mode pragma, execute code by injecting a
// script tag into the document.
- if ( code.indexOf("use strict") === 1 ) {
- script = document.createElement("script");
+ if ( code.indexOf( "use strict" ) === 1 ) {
+ script = document.createElement( "script" );
script.text = code;
document.head.appendChild( script ).parentNode.removeChild( script );
} else {
- // Otherwise, avoid the DOM node creation, insertion
- // and removal by using an indirect global eval
+
+ // Otherwise, avoid the DOM node creation, insertion
+ // and removal by using an indirect global eval
+
indirect( code );
}
}
@@ -341,49 +351,20 @@ jQuery.extend({
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
},
- // args is for internal usage only
- each: function( obj, callback, args ) {
- var value,
- i = 0,
- length = obj.length,
- isArray = isArraylike( obj );
+ each: function( obj, callback ) {
+ var length, i = 0;
- if ( args ) {
- if ( isArray ) {
- for ( ; i < length; i++ ) {
- value = callback.apply( obj[ i ], args );
-
- if ( value === false ) {
- break;
- }
- }
- } else {
- for ( i in obj ) {
- value = callback.apply( obj[ i ], args );
-
- if ( value === false ) {
- break;
- }
+ if ( isArrayLike( obj ) ) {
+ length = obj.length;
+ for ( ; i < length; i++ ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
}
}
-
- // A special, fast, case for the most common use of each
} else {
- if ( isArray ) {
- for ( ; i < length; i++ ) {
- value = callback.call( obj[ i ], i, obj[ i ] );
-
- if ( value === false ) {
- break;
- }
- }
- } else {
- for ( i in obj ) {
- value = callback.call( obj[ i ], i, obj[ i ] );
-
- if ( value === false ) {
- break;
- }
+ for ( i in obj ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
}
}
}
@@ -403,7 +384,7 @@ jQuery.extend({
var ret = results || [];
if ( arr != null ) {
- if ( isArraylike( Object(arr) ) ) {
+ if ( isArrayLike( Object( arr ) ) ) {
jQuery.merge( ret,
typeof arr === "string" ?
[ arr ] : arr
@@ -455,14 +436,13 @@ jQuery.extend({
// arg is for internal usage only
map: function( elems, callback, arg ) {
- var value,
+ var length, value,
i = 0,
- length = elems.length,
- isArray = isArraylike( elems ),
ret = [];
// Go through the array, translating each of the items to their new values
- if ( isArray ) {
+ if ( isArrayLike( elems ) ) {
+ length = elems.length;
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
@@ -523,38 +503,50 @@ jQuery.extend({
// jQuery.support is not used in Core but other projects attach their
// properties to it so it needs to exist.
support: support
-});
+} );
+
+// JSHint would error on this code due to the Symbol not being defined in ES5.
+// Defining this global in .jshintrc would create a danger of using the global
+// unguarded in another place, it seems safer to just disable JSHint for these
+// three lines.
+/* jshint ignore: start */
+if ( typeof Symbol === "function" ) {
+ jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+}
+/* jshint ignore: end */
// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+function( i, name ) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
+} );
-function isArraylike( obj ) {
- var length = obj.length,
+function isArrayLike( obj ) {
+
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = !!obj && "length" in obj && obj.length,
type = jQuery.type( obj );
if ( type === "function" || jQuery.isWindow( obj ) ) {
return false;
}
- if ( obj.nodeType === 1 && length ) {
- return true;
- }
-
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
var Sizzle =
/*!
- * Sizzle CSS Selector Engine v2.2.0-pre
+ * Sizzle CSS Selector Engine v2.2.1
* http://sizzlejs.com/
*
- * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
+ * Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
- * Date: 2014-12-16
+ * Date: 2015-10-17
*/
(function( window ) {
@@ -622,25 +614,21 @@ var i,
// Regular expressions
- // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ // http://www.w3.org/TR/css3-selectors/#whitespace
whitespace = "[\\x20\\t\\r\\n\\f]",
- // http://www.w3.org/TR/css3-syntax/#characters
- characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
- // Loosely modeled on CSS identifier characters
- // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
- // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
- identifier = characterEncoding.replace( "w", "w#" ),
+ // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
- attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
+ attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
// Operator (capture 2)
"*([*^$|!~]?=)" + whitespace +
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
"*\\]",
- pseudos = ":(" + characterEncoding + ")(?:\\((" +
+ pseudos = ":(" + identifier + ")(?:\\((" +
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
// 1. quoted (capture 3; capture 4 or capture 5)
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
@@ -663,9 +651,9 @@ var i,
ridentifier = new RegExp( "^" + identifier + "$" ),
matchExpr = {
- "ID": new RegExp( "^#(" + characterEncoding + ")" ),
- "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
- "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ID": new RegExp( "^#(" + identifier + ")" ),
+ "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+ "TAG": new RegExp( "^(" + identifier + "|[*])" ),
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
@@ -743,103 +731,129 @@ try {
}
function Sizzle( selector, context, results, seed ) {
- var match, elem, m, nodeType,
- // QSA vars
- i, groups, old, nid, newContext, newSelector;
+ var m, i, elem, nid, nidselect, match, groups, newSelector,
+ newContext = context && context.ownerDocument,
- if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
- setDocument( context );
- }
+ // nodeType defaults to 9, since context defaults to document
+ nodeType = context ? context.nodeType : 9;
- context = context || document;
results = results || [];
- nodeType = context.nodeType;
+ // Return early from calls with invalid selector or context
if ( typeof selector !== "string" || !selector ||
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
return results;
}
- if ( !seed && documentIsHTML ) {
+ // Try to shortcut find operations (as opposed to filters) in HTML documents
+ if ( !seed ) {
+
+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
+ }
+ context = context || document;
+
+ if ( documentIsHTML ) {
+
+ // If the selector is sufficiently simple, try using a "get*By*" DOM method
+ // (excepting DocumentFragment context, where the methods don't exist)
+ if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+
+ // ID selector
+ if ( (m = match[1]) ) {
+
+ // Document context
+ if ( nodeType === 9 ) {
+ if ( (elem = context.getElementById( m )) ) {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+
+ // Element context
+ } else {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( newContext && (elem = newContext.getElementById( m )) &&
+ contains( context, elem ) &&
+ elem.id === m ) {
- // Try to shortcut find operations when possible (e.g., not under DocumentFragment)
- if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
- // Speed-up: Sizzle("#ID")
- if ( (m = match[1]) ) {
- if ( nodeType === 9 ) {
- elem = context.getElementById( m );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document (jQuery #6963)
- if ( elem && elem.parentNode ) {
- // Handle the case where IE, Opera, and Webkit return items
- // by name instead of ID
- if ( elem.id === m ) {
results.push( elem );
return results;
}
- } else {
- return results;
}
- } else {
- // Context is not a document
- if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
- contains( context, elem ) && elem.id === m ) {
- results.push( elem );
- return results;
- }
- }
- // Speed-up: Sizzle("TAG")
- } else if ( match[2] ) {
- push.apply( results, context.getElementsByTagName( selector ) );
- return results;
-
- // Speed-up: Sizzle(".CLASS")
- } else if ( (m = match[3]) && support.getElementsByClassName ) {
- push.apply( results, context.getElementsByClassName( m ) );
- return results;
- }
- }
-
- // QSA path
- if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
- nid = old = expando;
- newContext = context;
- newSelector = nodeType !== 1 && selector;
-
- // qSA works strangely on Element-rooted queries
- // We can work around this by specifying an extra ID on the root
- // and working up from there (Thanks to Andrew Dupont for the technique)
- // IE 8 doesn't work on object elements
- if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
- groups = tokenize( selector );
-
- if ( (old = context.getAttribute("id")) ) {
- nid = old.replace( rescape, "\\$&" );
- } else {
- context.setAttribute( "id", nid );
- }
- nid = "[id='" + nid + "'] ";
-
- i = groups.length;
- while ( i-- ) {
- groups[i] = nid + toSelector( groups[i] );
- }
- newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
- newSelector = groups.join(",");
- }
-
- if ( newSelector ) {
- try {
- push.apply( results,
- newContext.querySelectorAll( newSelector )
- );
+ // Type selector
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
return results;
- } catch(qsaError) {
- } finally {
- if ( !old ) {
- context.removeAttribute("id");
+
+ // Class selector
+ } else if ( (m = match[3]) && support.getElementsByClassName &&
+ context.getElementsByClassName ) {
+
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // Take advantage of querySelectorAll
+ if ( support.qsa &&
+ !compilerCache[ selector + " " ] &&
+ (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+
+ if ( nodeType !== 1 ) {
+ newContext = context;
+ newSelector = selector;
+
+ // qSA looks outside Element context, which is not what we want
+ // Thanks to Andrew Dupont for this workaround technique
+ // Support: IE <=8
+ // Exclude object elements
+ } else if ( context.nodeName.toLowerCase() !== "object" ) {
+
+ // Capture the context ID, setting it first if necessary
+ if ( (nid = context.getAttribute( "id" )) ) {
+ nid = nid.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", (nid = expando) );
+ }
+
+ // Prefix every selector in the list
+ groups = tokenize( selector );
+ i = groups.length;
+ nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
+ while ( i-- ) {
+ groups[i] = nidselect + " " + toSelector( groups[i] );
+ }
+ newSelector = groups.join( "," );
+
+ // Expand context for sibling selectors
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+ context;
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch ( qsaError ) {
+ } finally {
+ if ( nid === expando ) {
+ context.removeAttribute( "id" );
+ }
}
}
}
@@ -852,7 +866,7 @@ function Sizzle( selector, context, results, seed ) {
/**
* Create key-value caches of limited size
- * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ * @returns {function(string, object)} Returns the Object data after storing it on itself with
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
* deleting the oldest entry
*/
@@ -907,7 +921,7 @@ function assert( fn ) {
*/
function addHandle( attrs, handler ) {
var arr = attrs.split("|"),
- i = attrs.length;
+ i = arr.length;
while ( i-- ) {
Expr.attrHandle[ arr[i] ] = handler;
@@ -1020,33 +1034,29 @@ setDocument = Sizzle.setDocument = function( node ) {
var hasCompare, parent,
doc = node ? node.ownerDocument || node : preferredDoc;
- // If no document and documentElement is available, return
+ // Return early if doc is invalid or already selected
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
return document;
}
- // Set our document
+ // Update global variables
document = doc;
- docElem = doc.documentElement;
- parent = doc.defaultView;
+ docElem = document.documentElement;
+ documentIsHTML = !isXML( document );
- // Support: IE>8
- // If iframe document is assigned to "document" variable and if iframe has been reloaded,
- // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
- // IE6-8 do not support the defaultView property so parent will be undefined
- if ( parent && parent !== parent.top ) {
- // IE11 does not have attachEvent, so all must suffer
+ // Support: IE 9-11, Edge
+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+ if ( (parent = document.defaultView) && parent.top !== parent ) {
+ // Support: IE 11
if ( parent.addEventListener ) {
parent.addEventListener( "unload", unloadHandler, false );
+
+ // Support: IE 9 - 10 only
} else if ( parent.attachEvent ) {
parent.attachEvent( "onunload", unloadHandler );
}
}
- /* Support tests
- ---------------------------------------------------------------------- */
- documentIsHTML = !isXML( doc );
-
/* Attributes
---------------------------------------------------------------------- */
@@ -1063,12 +1073,12 @@ setDocument = Sizzle.setDocument = function( node ) {
// Check if getElementsByTagName("*") returns only elements
support.getElementsByTagName = assert(function( div ) {
- div.appendChild( doc.createComment("") );
+ div.appendChild( document.createComment("") );
return !div.getElementsByTagName("*").length;
});
// Support: IE<9
- support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
+ support.getElementsByClassName = rnative.test( document.getElementsByClassName );
// Support: IE<10
// Check if getElementById returns elements by name
@@ -1076,7 +1086,7 @@ setDocument = Sizzle.setDocument = function( node ) {
// so use a roundabout getElementsByName test
support.getById = assert(function( div ) {
docElem.appendChild( div ).id = expando;
- return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+ return !document.getElementsByName || !document.getElementsByName( expando ).length;
});
// ID find and filter
@@ -1084,9 +1094,7 @@ setDocument = Sizzle.setDocument = function( node ) {
Expr.find["ID"] = function( id, context ) {
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
var m = context.getElementById( id );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- return m && m.parentNode ? [ m ] : [];
+ return m ? [ m ] : [];
}
};
Expr.filter["ID"] = function( id ) {
@@ -1103,7 +1111,8 @@ setDocument = Sizzle.setDocument = function( node ) {
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
- var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ var node = typeof elem.getAttributeNode !== "undefined" &&
+ elem.getAttributeNode("id");
return node && node.value === attrId;
};
};
@@ -1143,7 +1152,7 @@ setDocument = Sizzle.setDocument = function( node ) {
// Class
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
- if ( documentIsHTML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
return context.getElementsByClassName( className );
}
};
@@ -1163,7 +1172,7 @@ setDocument = Sizzle.setDocument = function( node ) {
// See http://bugs.jquery.com/ticket/13378
rbuggyQSA = [];
- if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+ if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
// Build QSA regex
// Regex strategy adopted from Diego Perini
assert(function( div ) {
@@ -1173,7 +1182,7 @@ setDocument = Sizzle.setDocument = function( node ) {
// since its presence should be enough
// http://bugs.jquery.com/ticket/12359
docElem.appendChild( div ).innerHTML = "
" +
- "
" +
+ "" +
" ";
// Support: IE8, Opera 11-12.16
@@ -1190,7 +1199,7 @@ setDocument = Sizzle.setDocument = function( node ) {
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
}
- // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+
+ // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
rbuggyQSA.push("~=");
}
@@ -1213,7 +1222,7 @@ setDocument = Sizzle.setDocument = function( node ) {
assert(function( div ) {
// Support: Windows 8 Native Apps
// The type and name attributes are restricted during .innerHTML assignment
- var input = doc.createElement("input");
+ var input = document.createElement("input");
input.setAttribute( "type", "hidden" );
div.appendChild( input ).setAttribute( "name", "D" );
@@ -1261,7 +1270,7 @@ setDocument = Sizzle.setDocument = function( node ) {
hasCompare = rnative.test( docElem.compareDocumentPosition );
// Element contains another
- // Purposefully does not implement inclusive descendent
+ // Purposefully self-exclusive
// As in, an element does not contain itself
contains = hasCompare || rnative.test( docElem.contains ) ?
function( a, b ) {
@@ -1315,10 +1324,10 @@ setDocument = Sizzle.setDocument = function( node ) {
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
// Choose the first element that is related to our preferred document
- if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+ if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
return -1;
}
- if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+ if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
return 1;
}
@@ -1346,8 +1355,8 @@ setDocument = Sizzle.setDocument = function( node ) {
// Parentless nodes are either documents or disconnected
if ( !aup || !bup ) {
- return a === doc ? -1 :
- b === doc ? 1 :
+ return a === document ? -1 :
+ b === document ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
@@ -1384,7 +1393,7 @@ setDocument = Sizzle.setDocument = function( node ) {
0;
};
- return doc;
+ return document;
};
Sizzle.matches = function( expr, elements ) {
@@ -1401,6 +1410,7 @@ Sizzle.matchesSelector = function( elem, expr ) {
expr = expr.replace( rattributeQuotes, "='$1']" );
if ( support.matchesSelector && documentIsHTML &&
+ !compilerCache[ expr + " " ] &&
( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
@@ -1674,11 +1684,12 @@ Expr = Sizzle.selectors = {
} :
function( elem, context, xml ) {
- var cache, outerCache, node, diff, nodeIndex, start,
+ var cache, uniqueCache, outerCache, node, nodeIndex, start,
dir = simple !== forward ? "nextSibling" : "previousSibling",
parent = elem.parentNode,
name = ofType && elem.nodeName.toLowerCase(),
- useCache = !xml && !ofType;
+ useCache = !xml && !ofType,
+ diff = false;
if ( parent ) {
@@ -1687,7 +1698,10 @@ Expr = Sizzle.selectors = {
while ( dir ) {
node = elem;
while ( (node = node[ dir ]) ) {
- if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+ if ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) {
+
return false;
}
}
@@ -1701,11 +1715,21 @@ Expr = Sizzle.selectors = {
// non-xml :nth-child(...) stores cache data on `parent`
if ( forward && useCache ) {
+
// Seek `elem` from a previously-cached index
- outerCache = parent[ expando ] || (parent[ expando ] = {});
- cache = outerCache[ type ] || [];
- nodeIndex = cache[0] === dirruns && cache[1];
- diff = cache[0] === dirruns && cache[2];
+
+ // ...in a gzip-friendly way
+ node = parent;
+ outerCache = node[ expando ] || (node[ expando ] = {});
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ (outerCache[ node.uniqueID ] = {});
+
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex && cache[ 2 ];
node = nodeIndex && parent.childNodes[ nodeIndex ];
while ( (node = ++nodeIndex && node && node[ dir ] ||
@@ -1715,29 +1739,55 @@ Expr = Sizzle.selectors = {
// When found, cache indexes on `parent` and break
if ( node.nodeType === 1 && ++diff && node === elem ) {
- outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
break;
}
}
- // Use previously-cached element index if available
- } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
- diff = cache[1];
-
- // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
} else {
- // Use the same loop as above to seek `elem` from the start
- while ( (node = ++nodeIndex && node && node[ dir ] ||
- (diff = nodeIndex = 0) || start.pop()) ) {
+ // Use previously-cached element index if available
+ if ( useCache ) {
+ // ...in a gzip-friendly way
+ node = elem;
+ outerCache = node[ expando ] || (node[ expando ] = {});
- if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
- // Cache the index of each encountered element
- if ( useCache ) {
- (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
- }
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ (outerCache[ node.uniqueID ] = {});
- if ( node === elem ) {
- break;
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex;
+ }
+
+ // xml :nth-child(...)
+ // or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ if ( diff === false ) {
+ // Use the same loop as above to seek `elem` from the start
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ if ( ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) &&
+ ++diff ) {
+
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ outerCache = node[ expando ] || (node[ expando ] = {});
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ (outerCache[ node.uniqueID ] = {});
+
+ uniqueCache[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
}
}
}
@@ -2099,10 +2149,10 @@ function addCombinator( matcher, combinator, base ) {
// Check against all ancestor/preceding elements
function( elem, context, xml ) {
- var oldCache, outerCache,
+ var oldCache, uniqueCache, outerCache,
newCache = [ dirruns, doneName ];
- // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
if ( xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
@@ -2115,14 +2165,19 @@ function addCombinator( matcher, combinator, base ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
outerCache = elem[ expando ] || (elem[ expando ] = {});
- if ( (oldCache = outerCache[ dir ]) &&
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
+
+ if ( (oldCache = uniqueCache[ dir ]) &&
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
// Assign to newCache so results back-propagate to previous elements
return (newCache[ 2 ] = oldCache[ 2 ]);
} else {
// Reuse newcache so results back-propagate to previous elements
- outerCache[ dir ] = newCache;
+ uniqueCache[ dir ] = newCache;
// A match means we're done; a fail means we have to keep checking
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
@@ -2347,18 +2402,21 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
len = elems.length;
if ( outermost ) {
- outermostContext = context !== document && context;
+ outermostContext = context === document || context || outermost;
}
// Add elements passing elementMatchers directly to results
- // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
// Support: IE<9, Safari
// Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
if ( byElement && elem ) {
j = 0;
+ if ( !context && elem.ownerDocument !== document ) {
+ setDocument( elem );
+ xml = !documentIsHTML;
+ }
while ( (matcher = elementMatchers[j++]) ) {
- if ( matcher( elem, context, xml ) ) {
+ if ( matcher( elem, context || document, xml) ) {
results.push( elem );
break;
}
@@ -2382,8 +2440,17 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
}
}
- // Apply set filters to unmatched elements
+ // `i` is now the count of elements visited above, and adding it to `matchedCount`
+ // makes the latter nonnegative.
matchedCount += i;
+
+ // Apply set filters to unmatched elements
+ // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+ // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+ // no element matchers and no seed.
+ // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+ // case, which will result in a "00" `matchedCount` that differs from `i` but is also
+ // numerically zero.
if ( bySet && i !== matchedCount ) {
j = 0;
while ( (matcher = setMatchers[j++]) ) {
@@ -2475,10 +2542,11 @@ select = Sizzle.select = function( selector, context, results, seed ) {
results = results || [];
- // Try to minimize operations if there is no seed and only one group
+ // Try to minimize operations if there is only one selector in the list and no seed
+ // (the latter of which guarantees us context)
if ( match.length === 1 ) {
- // Take a shortcut and set the context if the root selector is an ID
+ // Reduce context if the leading compound selector is an ID
tokens = match[0] = match[0].slice( 0 );
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
support.getById && context.nodeType === 9 && documentIsHTML &&
@@ -2533,7 +2601,7 @@ select = Sizzle.select = function( selector, context, results, seed ) {
context,
!documentIsHTML,
results,
- rsibling.test( selector ) && testContext( context.parentNode ) || context
+ !context || rsibling.test( selector ) && testContext( context.parentNode ) || context
);
return results;
};
@@ -2609,17 +2677,46 @@ return Sizzle;
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.pseudos;
-jQuery.unique = Sizzle.uniqueSort;
+jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
+var dir = function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+};
+
+
+var siblings = function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+};
+
+
var rneedsContext = jQuery.expr.match.needsContext;
-var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
+var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ );
@@ -2631,14 +2728,14 @@ function winnow( elements, qualifier, not ) {
return jQuery.grep( elements, function( elem, i ) {
/* jshint -W018 */
return !!qualifier.call( elem, i, elem ) !== not;
- });
+ } );
}
if ( qualifier.nodeType ) {
return jQuery.grep( elements, function( elem ) {
return ( elem === qualifier ) !== not;
- });
+ } );
}
@@ -2651,8 +2748,8 @@ function winnow( elements, qualifier, not ) {
}
return jQuery.grep( elements, function( elem ) {
- return ( indexOf.call( qualifier, elem ) >= 0 ) !== not;
- });
+ return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+ } );
}
jQuery.filter = function( expr, elems, not ) {
@@ -2666,10 +2763,10 @@ jQuery.filter = function( expr, elems, not ) {
jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
return elem.nodeType === 1;
- }));
+ } ) );
};
-jQuery.fn.extend({
+jQuery.fn.extend( {
find: function( selector ) {
var i,
len = this.length,
@@ -2677,13 +2774,13 @@ jQuery.fn.extend({
self = this;
if ( typeof selector !== "string" ) {
- return this.pushStack( jQuery( selector ).filter(function() {
+ return this.pushStack( jQuery( selector ).filter( function() {
for ( i = 0; i < len; i++ ) {
if ( jQuery.contains( self[ i ], this ) ) {
return true;
}
}
- }) );
+ } ) );
}
for ( i = 0; i < len; i++ ) {
@@ -2696,10 +2793,10 @@ jQuery.fn.extend({
return ret;
},
filter: function( selector ) {
- return this.pushStack( winnow(this, selector || [], false) );
+ return this.pushStack( winnow( this, selector || [], false ) );
},
not: function( selector ) {
- return this.pushStack( winnow(this, selector || [], true) );
+ return this.pushStack( winnow( this, selector || [], true ) );
},
is: function( selector ) {
return !!winnow(
@@ -2713,7 +2810,7 @@ jQuery.fn.extend({
false
).length;
}
-});
+} );
// Initialize a jQuery object
@@ -2727,7 +2824,7 @@ var rootjQuery,
// Strict HTML recognition (#11290: must start with <)
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
- init = jQuery.fn.init = function( selector, context ) {
+ init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
@@ -2735,9 +2832,16 @@ var rootjQuery,
return this;
}
+ // Method init() accepts an alternate rootjQuery
+ // so migrate can support jQuery.sub (gh-2101)
+ root = root || rootjQuery;
+
// Handle HTML strings
if ( typeof selector === "string" ) {
- if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
+ if ( selector[ 0 ] === "<" &&
+ selector[ selector.length - 1 ] === ">" &&
+ selector.length >= 3 ) {
+
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ];
@@ -2746,23 +2850,24 @@ var rootjQuery,
}
// Match html or make sure no context is specified for #id
- if ( match && (match[1] || !context) ) {
+ if ( match && ( match[ 1 ] || !context ) ) {
// HANDLE: $(html) -> $(array)
- if ( match[1] ) {
- context = context instanceof jQuery ? context[0] : context;
+ if ( match[ 1 ] ) {
+ context = context instanceof jQuery ? context[ 0 ] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge( this, jQuery.parseHTML(
- match[1],
+ match[ 1 ],
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
// HANDLE: $(html, props)
- if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
+
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
@@ -2778,14 +2883,15 @@ var rootjQuery,
// HANDLE: $(#id)
} else {
- elem = document.getElementById( match[2] );
+ elem = document.getElementById( match[ 2 ] );
// Support: Blackberry 4.6
// gEBID returns nodes no longer in the document (#6963)
if ( elem && elem.parentNode ) {
+
// Inject the element directly into the jQuery object
this.length = 1;
- this[0] = elem;
+ this[ 0 ] = elem;
}
this.context = document;
@@ -2795,7 +2901,7 @@ var rootjQuery,
// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
- return ( context || rootjQuery ).find( selector );
+ return ( context || root ).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
@@ -2805,15 +2911,16 @@ var rootjQuery,
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
- this.context = this[0] = selector;
+ this.context = this[ 0 ] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
- return typeof rootjQuery.ready !== "undefined" ?
- rootjQuery.ready( selector ) :
+ return root.ready !== undefined ?
+ root.ready( selector ) :
+
// Execute immediately if ready is not present
selector( jQuery );
}
@@ -2834,6 +2941,7 @@ rootjQuery = jQuery( document );
var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
// Methods guaranteed to produce a unique set when starting from a unique set
guaranteedUnique = {
children: true,
@@ -2842,48 +2950,19 @@ var rparentsprev = /^(?:parents|prev(?:Until|All))/,
prev: true
};
-jQuery.extend({
- dir: function( elem, dir, until ) {
- var matched = [],
- truncate = until !== undefined;
-
- while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
- if ( elem.nodeType === 1 ) {
- if ( truncate && jQuery( elem ).is( until ) ) {
- break;
- }
- matched.push( elem );
- }
- }
- return matched;
- },
-
- sibling: function( n, elem ) {
- var matched = [];
-
- for ( ; n; n = n.nextSibling ) {
- if ( n.nodeType === 1 && n !== elem ) {
- matched.push( n );
- }
- }
-
- return matched;
- }
-});
-
-jQuery.fn.extend({
+jQuery.fn.extend( {
has: function( target ) {
var targets = jQuery( target, this ),
l = targets.length;
- return this.filter(function() {
+ return this.filter( function() {
var i = 0;
for ( ; i < l; i++ ) {
- if ( jQuery.contains( this, targets[i] ) ) {
+ if ( jQuery.contains( this, targets[ i ] ) ) {
return true;
}
}
- });
+ } );
},
closest: function( selectors, context ) {
@@ -2896,14 +2975,15 @@ jQuery.fn.extend({
0;
for ( ; i < l; i++ ) {
- for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+ for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
// Always skip document fragments
- if ( cur.nodeType < 11 && (pos ?
- pos.index(cur) > -1 :
+ if ( cur.nodeType < 11 && ( pos ?
+ pos.index( cur ) > -1 :
// Don't pass non-elements to Sizzle
cur.nodeType === 1 &&
- jQuery.find.matchesSelector(cur, selectors)) ) {
+ jQuery.find.matchesSelector( cur, selectors ) ) ) {
matched.push( cur );
break;
@@ -2911,7 +2991,7 @@ jQuery.fn.extend({
}
}
- return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+ return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
},
// Determine the position of an element within the set
@@ -2937,7 +3017,7 @@ jQuery.fn.extend({
add: function( selector, context ) {
return this.pushStack(
- jQuery.unique(
+ jQuery.uniqueSort(
jQuery.merge( this.get(), jQuery( selector, context ) )
)
);
@@ -2945,26 +3025,26 @@ jQuery.fn.extend({
addBack: function( selector ) {
return this.add( selector == null ?
- this.prevObject : this.prevObject.filter(selector)
+ this.prevObject : this.prevObject.filter( selector )
);
}
-});
+} );
function sibling( cur, dir ) {
- while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
+ while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
return cur;
}
-jQuery.each({
+jQuery.each( {
parent: function( elem ) {
var parent = elem.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
},
parents: function( elem ) {
- return jQuery.dir( elem, "parentNode" );
+ return dir( elem, "parentNode" );
},
parentsUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "parentNode", until );
+ return dir( elem, "parentNode", until );
},
next: function( elem ) {
return sibling( elem, "nextSibling" );
@@ -2973,22 +3053,22 @@ jQuery.each({
return sibling( elem, "previousSibling" );
},
nextAll: function( elem ) {
- return jQuery.dir( elem, "nextSibling" );
+ return dir( elem, "nextSibling" );
},
prevAll: function( elem ) {
- return jQuery.dir( elem, "previousSibling" );
+ return dir( elem, "previousSibling" );
},
nextUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "nextSibling", until );
+ return dir( elem, "nextSibling", until );
},
prevUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "previousSibling", until );
+ return dir( elem, "previousSibling", until );
},
siblings: function( elem ) {
- return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ return siblings( ( elem.parentNode || {} ).firstChild, elem );
},
children: function( elem ) {
- return jQuery.sibling( elem.firstChild );
+ return siblings( elem.firstChild );
},
contents: function( elem ) {
return elem.contentDocument || jQuery.merge( [], elem.childNodes );
@@ -3006,9 +3086,10 @@ jQuery.each({
}
if ( this.length > 1 ) {
+
// Remove duplicates
if ( !guaranteedUnique[ name ] ) {
- jQuery.unique( matched );
+ jQuery.uniqueSort( matched );
}
// Reverse order for parents* and prev-derivatives
@@ -3019,20 +3100,17 @@ jQuery.each({
return this.pushStack( matched );
};
-});
-var rnotwhite = (/\S+/g);
+} );
+var rnotwhite = ( /\S+/g );
-// String to Object options format cache
-var optionsCache = {};
-
-// Convert String-formatted options into Object-formatted ones and store in cache
+// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
- var object = optionsCache[ options ] = {};
+ var object = {};
jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
- });
+ } );
return object;
}
@@ -3063,156 +3141,186 @@ jQuery.Callbacks = function( options ) {
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
- ( optionsCache[ options ] || createOptions( options ) ) :
+ createOptions( options ) :
jQuery.extend( {}, options );
- var // Last fire value (for non-forgettable lists)
+ var // Flag to know if list is currently firing
+ firing,
+
+ // Last fire value for non-forgettable lists
memory,
+
// Flag to know if list was already fired
fired,
- // Flag to know if list is currently firing
- firing,
- // First callback to fire (used internally by add and fireWith)
- firingStart,
- // End of the loop when firing
- firingLength,
- // Index of currently firing callback (modified by remove if needed)
- firingIndex,
+
+ // Flag to prevent firing
+ locked,
+
// Actual callback list
list = [],
- // Stack of fire calls for repeatable lists
- stack = !options.once && [],
+
+ // Queue of execution data for repeatable lists
+ queue = [],
+
+ // Index of currently firing callback (modified by add/remove as needed)
+ firingIndex = -1,
+
// Fire callbacks
- fire = function( data ) {
- memory = options.memory && data;
- fired = true;
- firingIndex = firingStart || 0;
- firingStart = 0;
- firingLength = list.length;
- firing = true;
- for ( ; list && firingIndex < firingLength; firingIndex++ ) {
- if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
- memory = false; // To prevent further calls using add
- break;
+ fire = function() {
+
+ // Enforce single-firing
+ locked = options.once;
+
+ // Execute callbacks for all pending executions,
+ // respecting firingIndex overrides and runtime changes
+ fired = firing = true;
+ for ( ; queue.length; firingIndex = -1 ) {
+ memory = queue.shift();
+ while ( ++firingIndex < list.length ) {
+
+ // Run callback and check for early termination
+ if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+ options.stopOnFalse ) {
+
+ // Jump to end and forget the data so .add doesn't re-fire
+ firingIndex = list.length;
+ memory = false;
+ }
}
}
+
+ // Forget the data if we're done with it
+ if ( !options.memory ) {
+ memory = false;
+ }
+
firing = false;
- if ( list ) {
- if ( stack ) {
- if ( stack.length ) {
- fire( stack.shift() );
- }
- } else if ( memory ) {
+
+ // Clean up if we're done firing for good
+ if ( locked ) {
+
+ // Keep an empty list if we have data for future add calls
+ if ( memory ) {
list = [];
+
+ // Otherwise, this object is spent
} else {
- self.disable();
+ list = "";
}
}
},
+
// Actual Callbacks object
self = {
+
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
- // First, we save the current length
- var start = list.length;
- (function add( args ) {
+
+ // If we have memory from a past run, we should fire after adding
+ if ( memory && !firing ) {
+ firingIndex = list.length - 1;
+ queue.push( memory );
+ }
+
+ ( function add( args ) {
jQuery.each( args, function( _, arg ) {
- var type = jQuery.type( arg );
- if ( type === "function" ) {
+ if ( jQuery.isFunction( arg ) ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
- } else if ( arg && arg.length && type !== "string" ) {
+ } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
+
// Inspect recursively
add( arg );
}
- });
- })( arguments );
- // Do we need to add the callbacks to the
- // current firing batch?
- if ( firing ) {
- firingLength = list.length;
- // With memory, if we're not firing then
- // we should call right away
- } else if ( memory ) {
- firingStart = start;
- fire( memory );
+ } );
+ } )( arguments );
+
+ if ( memory && !firing ) {
+ fire();
}
}
return this;
},
+
// Remove a callback from the list
remove: function() {
- if ( list ) {
- jQuery.each( arguments, function( _, arg ) {
- var index;
- while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
- list.splice( index, 1 );
- // Handle firing indexes
- if ( firing ) {
- if ( index <= firingLength ) {
- firingLength--;
- }
- if ( index <= firingIndex ) {
- firingIndex--;
- }
- }
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+
+ // Handle firing indexes
+ if ( index <= firingIndex ) {
+ firingIndex--;
}
- });
- }
+ }
+ } );
return this;
},
+
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
has: function( fn ) {
- return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+ return fn ?
+ jQuery.inArray( fn, list ) > -1 :
+ list.length > 0;
},
+
// Remove all callbacks from the list
empty: function() {
- list = [];
- firingLength = 0;
- return this;
- },
- // Have the list do nothing anymore
- disable: function() {
- list = stack = memory = undefined;
- return this;
- },
- // Is it disabled?
- disabled: function() {
- return !list;
- },
- // Lock the list in its current state
- lock: function() {
- stack = undefined;
- if ( !memory ) {
- self.disable();
+ if ( list ) {
+ list = [];
}
return this;
},
- // Is it locked?
- locked: function() {
- return !stack;
+
+ // Disable .fire and .add
+ // Abort any current/pending executions
+ // Clear all callbacks and values
+ disable: function() {
+ locked = queue = [];
+ list = memory = "";
+ return this;
},
+ disabled: function() {
+ return !list;
+ },
+
+ // Disable .fire
+ // Also disable .add unless we have memory (since it would have no effect)
+ // Abort any pending executions
+ lock: function() {
+ locked = queue = [];
+ if ( !memory ) {
+ list = memory = "";
+ }
+ return this;
+ },
+ locked: function() {
+ return !!locked;
+ },
+
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
- if ( list && ( !fired || stack ) ) {
+ if ( !locked ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
- if ( firing ) {
- stack.push( args );
- } else {
- fire( args );
+ queue.push( args );
+ if ( !firing ) {
+ fire();
}
}
return this;
},
+
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
+
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
@@ -3223,14 +3331,15 @@ jQuery.Callbacks = function( options ) {
};
-jQuery.extend({
+jQuery.extend( {
Deferred: function( func ) {
var tuples = [
+
// action, add listener, listener list, final state
- [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
- [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
- [ "notify", "progress", jQuery.Callbacks("memory") ]
+ [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks( "memory" ) ]
],
state = "pending",
promise = {
@@ -3243,25 +3352,30 @@ jQuery.extend({
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
- return jQuery.Deferred(function( newDefer ) {
+ return jQuery.Deferred( function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+
// deferred[ done | fail | progress ] for forwarding actions to newDefer
- deferred[ tuple[1] ](function() {
+ deferred[ tuple[ 1 ] ]( function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
+ .progress( newDefer.notify )
.done( newDefer.resolve )
- .fail( newDefer.reject )
- .progress( newDefer.notify );
+ .fail( newDefer.reject );
} else {
- newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ newDefer[ tuple[ 0 ] + "With" ](
+ this === promise ? newDefer.promise() : this,
+ fn ? [ returned ] : arguments
+ );
}
- });
- });
+ } );
+ } );
fns = null;
- }).promise();
+ } ).promise();
},
+
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
@@ -3279,11 +3393,12 @@ jQuery.extend({
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
- promise[ tuple[1] ] = list.add;
+ promise[ tuple[ 1 ] ] = list.add;
// Handle state
if ( stateString ) {
- list.add(function() {
+ list.add( function() {
+
// state = [ resolved | rejected ]
state = stateString;
@@ -3292,12 +3407,12 @@ jQuery.extend({
}
// deferred[ resolve | reject | notify ]
- deferred[ tuple[0] ] = function() {
- deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ deferred[ tuple[ 0 ] ] = function() {
+ deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
- deferred[ tuple[0] + "With" ] = list.fireWith;
- });
+ deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+ } );
// Make the deferred a promise
promise.promise( deferred );
@@ -3318,9 +3433,11 @@ jQuery.extend({
length = resolveValues.length,
// the count of uncompleted subordinates
- remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+ remaining = length !== 1 ||
+ ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
- // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ // the master Deferred.
+ // If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for both resolve and progress values
@@ -3346,9 +3463,9 @@ jQuery.extend({
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
+ .progress( updateFunc( i, progressContexts, progressValues ) )
.done( updateFunc( i, resolveContexts, resolveValues ) )
- .fail( deferred.reject )
- .progress( updateFunc( i, progressContexts, progressValues ) );
+ .fail( deferred.reject );
} else {
--remaining;
}
@@ -3362,20 +3479,22 @@ jQuery.extend({
return deferred.promise();
}
-});
+} );
// The deferred used on DOM ready
var readyList;
jQuery.fn.ready = function( fn ) {
+
// Add the callback
jQuery.ready.promise().done( fn );
return this;
};
-jQuery.extend({
+jQuery.extend( {
+
// Is the DOM ready to be used? Set to true once it occurs.
isReady: false,
@@ -3417,14 +3536,14 @@ jQuery.extend({
jQuery( document ).off( "ready" );
}
}
-});
+} );
/**
* The ready event handler and self cleanup method
*/
function completed() {
- document.removeEventListener( "DOMContentLoaded", completed, false );
- window.removeEventListener( "load", completed, false );
+ document.removeEventListener( "DOMContentLoaded", completed );
+ window.removeEventListener( "load", completed );
jQuery.ready();
}
@@ -3433,20 +3552,23 @@ jQuery.ready.promise = function( obj ) {
readyList = jQuery.Deferred();
- // Catch cases where $(document).ready() is called after the browser event has already occurred.
- // We once tried to use readyState "interactive" here, but it caused issues like the one
- // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
- if ( document.readyState === "complete" ) {
+ // Catch cases where $(document).ready() is called
+ // after the browser event has already occurred.
+ // Support: IE9-10 only
+ // Older IE sometimes signals "interactive" too soon
+ if ( document.readyState === "complete" ||
+ ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
// Handle it asynchronously to allow scripts the opportunity to delay ready
- setTimeout( jQuery.ready );
+ window.setTimeout( jQuery.ready );
} else {
// Use the handy event callback
- document.addEventListener( "DOMContentLoaded", completed, false );
+ document.addEventListener( "DOMContentLoaded", completed );
// A fallback to window.onload, that will always work
- window.addEventListener( "load", completed, false );
+ window.addEventListener( "load", completed );
}
}
return readyList.promise( obj );
@@ -3460,7 +3582,7 @@ jQuery.ready.promise();
// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
-var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
len = elems.length,
bulk = key == null;
@@ -3469,7 +3591,7 @@ var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGe
if ( jQuery.type( key ) === "object" ) {
chainable = true;
for ( i in key ) {
- jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ access( elems, fn, i, key[ i ], true, emptyGet, raw );
}
// Sets one value
@@ -3481,6 +3603,7 @@ var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGe
}
if ( bulk ) {
+
// Bulk operations run against the entire set
if ( raw ) {
fn.call( elems, value );
@@ -3497,7 +3620,11 @@ var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGe
if ( fn ) {
for ( ; i < len; i++ ) {
- fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ fn(
+ elems[ i ], key, raw ?
+ value :
+ value.call( elems[ i ], i, fn( elems[ i ], key ) )
+ );
}
}
}
@@ -3508,14 +3635,10 @@ var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGe
// Gets
bulk ?
fn.call( elems ) :
- len ? fn( elems[0], key ) : emptyGet;
+ len ? fn( elems[ 0 ], key ) : emptyGet;
};
+var acceptData = function( owner ) {
-
-/**
- * Determines whether an object can have data
- */
-jQuery.acceptData = function( owner ) {
// Accepts only:
// - Node
// - Node.ELEMENT_NODE
@@ -3527,66 +3650,79 @@ jQuery.acceptData = function( owner ) {
};
-function Data() {
- // Support: Android<4,
- // Old WebKit does not have Object.preventExtensions/freeze method,
- // return new empty object instead with no [[set]] accessor
- Object.defineProperty( this.cache = {}, 0, {
- get: function() {
- return {};
- }
- });
+
+function Data() {
this.expando = jQuery.expando + Data.uid++;
}
Data.uid = 1;
-Data.accepts = jQuery.acceptData;
Data.prototype = {
- key: function( owner ) {
+
+ register: function( owner, initial ) {
+ var value = initial || {};
+
+ // If it is a node unlikely to be stringify-ed or looped over
+ // use plain assignment
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = value;
+
+ // Otherwise secure it in a non-enumerable, non-writable property
+ // configurability must be true to allow the property to be
+ // deleted with the delete operator
+ } else {
+ Object.defineProperty( owner, this.expando, {
+ value: value,
+ writable: true,
+ configurable: true
+ } );
+ }
+ return owner[ this.expando ];
+ },
+ cache: function( owner ) {
+
// We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335.
- // Always return the key for a frozen object.
- if ( !Data.accepts( owner ) ) {
- return 0;
+ // Always return an empty object.
+ if ( !acceptData( owner ) ) {
+ return {};
}
- var descriptor = {},
- // Check if the owner object already has a cache key
- unlock = owner[ this.expando ];
+ // Check if the owner object already has a cache
+ var value = owner[ this.expando ];
// If not, create one
- if ( !unlock ) {
- unlock = Data.uid++;
+ if ( !value ) {
+ value = {};
- // Secure it in a non-enumerable, non-writable property
- try {
- descriptor[ this.expando ] = { value: unlock };
- Object.defineProperties( owner, descriptor );
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return an empty object.
+ if ( acceptData( owner ) ) {
- // Support: Android<4
- // Fallback to a less secure definition
- } catch ( e ) {
- descriptor[ this.expando ] = unlock;
- jQuery.extend( owner, descriptor );
+ // If it is a node unlikely to be stringify-ed or looped over
+ // use plain assignment
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = value;
+
+ // Otherwise secure it in a non-enumerable property
+ // configurable must be true to allow the property to be
+ // deleted when data is removed
+ } else {
+ Object.defineProperty( owner, this.expando, {
+ value: value,
+ configurable: true
+ } );
+ }
}
}
- // Ensure the cache object
- if ( !this.cache[ unlock ] ) {
- this.cache[ unlock ] = {};
- }
-
- return unlock;
+ return value;
},
set: function( owner, data, value ) {
var prop,
- // There may be an unlock assigned to this node,
- // if there is no entry for this "owner", create one inline
- // and set the unlock as though an owner entry had always existed
- unlock = this.key( owner ),
- cache = this.cache[ unlock ];
+ cache = this.cache( owner );
// Handle: [ owner, key, value ] args
if ( typeof data === "string" ) {
@@ -3594,30 +3730,22 @@ Data.prototype = {
// Handle: [ owner, { properties } ] args
} else {
- // Fresh assignments by object are shallow copied
- if ( jQuery.isEmptyObject( cache ) ) {
- jQuery.extend( this.cache[ unlock ], data );
- // Otherwise, copy the properties one-by-one to the cache object
- } else {
- for ( prop in data ) {
- cache[ prop ] = data[ prop ];
- }
+
+ // Copy the properties one-by-one to the cache object
+ for ( prop in data ) {
+ cache[ prop ] = data[ prop ];
}
}
return cache;
},
get: function( owner, key ) {
- // Either a valid cache is found, or will be created.
- // New caches will be created and the unlock returned,
- // allowing direct access to the newly created
- // empty data object. A valid owner object must be provided.
- var cache = this.cache[ this.key( owner ) ];
-
return key === undefined ?
- cache : cache[ key ];
+ this.cache( owner ) :
+ owner[ this.expando ] && owner[ this.expando ][ key ];
},
access: function( owner, key, value ) {
var stored;
+
// In cases where either:
//
// 1. No key was specified
@@ -3630,15 +3758,15 @@ Data.prototype = {
// 2. The data stored at the key
//
if ( key === undefined ||
- ((key && typeof key === "string") && value === undefined) ) {
+ ( ( key && typeof key === "string" ) && value === undefined ) ) {
stored = this.get( owner, key );
return stored !== undefined ?
- stored : this.get( owner, jQuery.camelCase(key) );
+ stored : this.get( owner, jQuery.camelCase( key ) );
}
- // [*]When the key is not a string, or both a key and value
+ // When the key is not a string, or both a key and value
// are specified, set or extend (existing objects) with either:
//
// 1. An object of properties
@@ -3652,15 +3780,20 @@ Data.prototype = {
},
remove: function( owner, key ) {
var i, name, camel,
- unlock = this.key( owner ),
- cache = this.cache[ unlock ];
+ cache = owner[ this.expando ];
+
+ if ( cache === undefined ) {
+ return;
+ }
if ( key === undefined ) {
- this.cache[ unlock ] = {};
+ this.register( owner );
} else {
+
// Support array or space separated string of keys
if ( jQuery.isArray( key ) ) {
+
// If "name" is an array of keys...
// When data is initially created, via ("key", "val") signature,
// keys will be converted to camelCase.
@@ -3670,10 +3803,12 @@ Data.prototype = {
name = key.concat( key.map( jQuery.camelCase ) );
} else {
camel = jQuery.camelCase( key );
+
// Try the string as a key before any manipulation
if ( key in cache ) {
name = [ key, camel ];
} else {
+
// If a key with the spaces exists, use it.
// Otherwise, create an array by matching non-whitespace
name = camel;
@@ -3683,25 +3818,34 @@ Data.prototype = {
}
i = name.length;
+
while ( i-- ) {
delete cache[ name[ i ] ];
}
}
+
+ // Remove the expando if there's no more data
+ if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+ // Support: Chrome <= 35-45+
+ // Webkit & Blink performance suffers when deleting properties
+ // from DOM nodes, so set to undefined instead
+ // https://code.google.com/p/chromium/issues/detail?id=378607
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = undefined;
+ } else {
+ delete owner[ this.expando ];
+ }
+ }
},
hasData: function( owner ) {
- return !jQuery.isEmptyObject(
- this.cache[ owner[ this.expando ] ] || {}
- );
- },
- discard: function( owner ) {
- if ( owner[ this.expando ] ) {
- delete this.cache[ owner[ this.expando ] ];
- }
+ var cache = owner[ this.expando ];
+ return cache !== undefined && !jQuery.isEmptyObject( cache );
}
};
-var data_priv = new Data();
+var dataPriv = new Data();
-var data_user = new Data();
+var dataUser = new Data();
@@ -3716,7 +3860,7 @@ var data_user = new Data();
// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
- rmultiDash = /([A-Z])/g;
+ rmultiDash = /[A-Z]/g;
function dataAttr( elem, key, data ) {
var name;
@@ -3724,7 +3868,7 @@ function dataAttr( elem, key, data ) {
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
if ( data === undefined && elem.nodeType === 1 ) {
- name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+ name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
@@ -3732,14 +3876,15 @@ function dataAttr( elem, key, data ) {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
+
// Only convert to a number if it doesn't change the string
+data + "" === data ? +data :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
- } catch( e ) {}
+ } catch ( e ) {}
// Make sure we set the data so it isn't changed later
- data_user.set( elem, key, data );
+ dataUser.set( elem, key, data );
} else {
data = undefined;
}
@@ -3747,31 +3892,31 @@ function dataAttr( elem, key, data ) {
return data;
}
-jQuery.extend({
+jQuery.extend( {
hasData: function( elem ) {
- return data_user.hasData( elem ) || data_priv.hasData( elem );
+ return dataUser.hasData( elem ) || dataPriv.hasData( elem );
},
data: function( elem, name, data ) {
- return data_user.access( elem, name, data );
+ return dataUser.access( elem, name, data );
},
removeData: function( elem, name ) {
- data_user.remove( elem, name );
+ dataUser.remove( elem, name );
},
// TODO: Now that all calls to _data and _removeData have been replaced
- // with direct calls to data_priv methods, these can be deprecated.
+ // with direct calls to dataPriv methods, these can be deprecated.
_data: function( elem, name, data ) {
- return data_priv.access( elem, name, data );
+ return dataPriv.access( elem, name, data );
},
_removeData: function( elem, name ) {
- data_priv.remove( elem, name );
+ dataPriv.remove( elem, name );
}
-});
+} );
-jQuery.fn.extend({
+jQuery.fn.extend( {
data: function( key, value ) {
var i, name, data,
elem = this[ 0 ],
@@ -3780,9 +3925,9 @@ jQuery.fn.extend({
// Gets all values
if ( key === undefined ) {
if ( this.length ) {
- data = data_user.get( elem );
+ data = dataUser.get( elem );
- if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+ if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
i = attrs.length;
while ( i-- ) {
@@ -3791,12 +3936,12 @@ jQuery.fn.extend({
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
- name = jQuery.camelCase( name.slice(5) );
+ name = jQuery.camelCase( name.slice( 5 ) );
dataAttr( elem, name, data[ name ] );
}
}
}
- data_priv.set( elem, "hasDataAttrs", true );
+ dataPriv.set( elem, "hasDataAttrs", true );
}
}
@@ -3805,14 +3950,13 @@ jQuery.fn.extend({
// Sets multiple values
if ( typeof key === "object" ) {
- return this.each(function() {
- data_user.set( this, key );
- });
+ return this.each( function() {
+ dataUser.set( this, key );
+ } );
}
return access( this, function( value ) {
- var data,
- camelKey = jQuery.camelCase( key );
+ var data, camelKey;
// The calling jQuery object (element matches) is not empty
// (and therefore has an element appears at this[ 0 ]) and the
@@ -3820,16 +3964,24 @@ jQuery.fn.extend({
// will result in `undefined` for elem = this[ 0 ] which will
// throw an exception if an attempt to read a data cache is made.
if ( elem && value === undefined ) {
+
// Attempt to get data from the cache
// with the key as-is
- data = data_user.get( elem, key );
+ data = dataUser.get( elem, key ) ||
+
+ // Try to find dashed key if it exists (gh-2779)
+ // This is for 2.2.x only
+ dataUser.get( elem, key.replace( rmultiDash, "-$&" ).toLowerCase() );
+
if ( data !== undefined ) {
return data;
}
+ camelKey = jQuery.camelCase( key );
+
// Attempt to get data from the cache
// with the key camelized
- data = data_user.get( elem, camelKey );
+ data = dataUser.get( elem, camelKey );
if ( data !== undefined ) {
return data;
}
@@ -3846,46 +3998,48 @@ jQuery.fn.extend({
}
// Set the data...
- this.each(function() {
+ camelKey = jQuery.camelCase( key );
+ this.each( function() {
+
// First, attempt to store a copy or reference of any
// data that might've been store with a camelCased key.
- var data = data_user.get( this, camelKey );
+ var data = dataUser.get( this, camelKey );
// For HTML5 data-* attribute interop, we have to
// store property names with dashes in a camelCase form.
// This might not apply to all properties...*
- data_user.set( this, camelKey, value );
+ dataUser.set( this, camelKey, value );
// *... In the case of properties that might _actually_
// have dashes, we need to also store a copy of that
// unchanged property.
- if ( key.indexOf("-") !== -1 && data !== undefined ) {
- data_user.set( this, key, value );
+ if ( key.indexOf( "-" ) > -1 && data !== undefined ) {
+ dataUser.set( this, key, value );
}
- });
+ } );
}, null, value, arguments.length > 1, null, true );
},
removeData: function( key ) {
- return this.each(function() {
- data_user.remove( this, key );
- });
+ return this.each( function() {
+ dataUser.remove( this, key );
+ } );
}
-});
+} );
-jQuery.extend({
+jQuery.extend( {
queue: function( elem, type, data ) {
var queue;
if ( elem ) {
type = ( type || "fx" ) + "queue";
- queue = data_priv.get( elem, type );
+ queue = dataPriv.get( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
if ( !queue || jQuery.isArray( data ) ) {
- queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+ queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
} else {
queue.push( data );
}
@@ -3932,15 +4086,15 @@ jQuery.extend({
// Not public - generate a queueHooks object, or return the current one
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
- return data_priv.get( elem, key ) || data_priv.access( elem, key, {
- empty: jQuery.Callbacks("once memory").add(function() {
- data_priv.remove( elem, [ type + "queue", key ] );
- })
- });
+ return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+ empty: jQuery.Callbacks( "once memory" ).add( function() {
+ dataPriv.remove( elem, [ type + "queue", key ] );
+ } )
+ } );
}
-});
+} );
-jQuery.fn.extend({
+jQuery.fn.extend( {
queue: function( type, data ) {
var setter = 2;
@@ -3951,30 +4105,31 @@ jQuery.fn.extend({
}
if ( arguments.length < setter ) {
- return jQuery.queue( this[0], type );
+ return jQuery.queue( this[ 0 ], type );
}
return data === undefined ?
this :
- this.each(function() {
+ this.each( function() {
var queue = jQuery.queue( this, type, data );
// Ensure a hooks for this queue
jQuery._queueHooks( this, type );
- if ( type === "fx" && queue[0] !== "inprogress" ) {
+ if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
- });
+ } );
},
dequeue: function( type ) {
- return this.each(function() {
+ return this.each( function() {
jQuery.dequeue( this, type );
- });
+ } );
},
clearQueue: function( type ) {
return this.queue( type || "fx", [] );
},
+
// Get a promise resolved when queues of a certain type
// are emptied (fx is the type by default)
promise: function( type, obj ) {
@@ -3996,7 +4151,7 @@ jQuery.fn.extend({
type = type || "fx";
while ( i-- ) {
- tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+ tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
if ( tmp && tmp.empty ) {
count++;
tmp.empty.add( resolve );
@@ -4005,28 +4160,243 @@ jQuery.fn.extend({
resolve();
return defer.promise( obj );
}
-});
-var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
+} );
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
var isHidden = function( elem, el ) {
+
// isHidden might be called from jQuery#filter function;
// in that case, element will be second argument
elem = el || elem;
- return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+ return jQuery.css( elem, "display" ) === "none" ||
+ !jQuery.contains( elem.ownerDocument, elem );
};
-var rcheckableType = (/^(?:checkbox|radio)$/i);
+
+
+function adjustCSS( elem, prop, valueParts, tween ) {
+ var adjusted,
+ scale = 1,
+ maxIterations = 20,
+ currentValue = tween ?
+ function() { return tween.cur(); } :
+ function() { return jQuery.css( elem, prop, "" ); },
+ initial = currentValue(),
+ unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+ rcssNum.exec( jQuery.css( elem, prop ) );
+
+ if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+ // Trust units reported by jQuery.css
+ unit = unit || initialInUnit[ 3 ];
+
+ // Make sure we update the tween properties later on
+ valueParts = valueParts || [];
+
+ // Iteratively approximate from a nonzero starting point
+ initialInUnit = +initial || 1;
+
+ do {
+
+ // If previous iteration zeroed out, double until we get *something*.
+ // Use string for doubling so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ initialInUnit = initialInUnit / scale;
+ jQuery.style( elem, prop, initialInUnit + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // Break the loop if scale is unchanged or perfect, or if we've just had enough.
+ } while (
+ scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
+ );
+ }
+
+ if ( valueParts ) {
+ initialInUnit = +initialInUnit || +initial || 0;
+
+ // Apply relative offset (+=/-=) if specified
+ adjusted = valueParts[ 1 ] ?
+ initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+ +valueParts[ 2 ];
+ if ( tween ) {
+ tween.unit = unit;
+ tween.start = initialInUnit;
+ tween.end = adjusted;
+ }
+ }
+ return adjusted;
+}
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+var rtagName = ( /<([\w:-]+)/ );
+
+var rscriptType = ( /^$|\/(?:java|ecma)script/i );
-(function() {
+// We have to close these tags to support XHTML (#13200)
+var wrapMap = {
+
+ // Support: IE9
+ option: [ 1, "", " " ],
+
+ // XHTML parsers do not magically insert elements in the
+ // same way that tag soup parsers do. So we cannot shorten
+ // this by omitting or other required elements.
+ thead: [ 1, "" ],
+ col: [ 2, "" ],
+ tr: [ 2, "" ],
+ td: [ 3, "" ],
+
+ _default: [ 0, "", "" ]
+};
+
+// Support: IE9
+wrapMap.optgroup = wrapMap.option;
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+
+function getAll( context, tag ) {
+
+ // Support: IE9-11+
+ // Use typeof to avoid zero-argument method invocation on host objects (#15151)
+ var ret = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( tag || "*" ) :
+ typeof context.querySelectorAll !== "undefined" ?
+ context.querySelectorAll( tag || "*" ) :
+ [];
+
+ return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+ jQuery.merge( [ context ], ret ) :
+ ret;
+}
+
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ dataPriv.set(
+ elems[ i ],
+ "globalEval",
+ !refElements || dataPriv.get( refElements[ i ], "globalEval" )
+ );
+ }
+}
+
+
+var rhtml = /<|?\w+;/;
+
+function buildFragment( elems, context, scripts, selection, ignored ) {
+ var elem, tmp, tag, wrap, contains, j,
+ fragment = context.createDocumentFragment(),
+ nodes = [],
+ i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( jQuery.type( elem ) === "object" ) {
+
+ // Support: Android<4.1, PhantomJS<2
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Support: Android<4.1, PhantomJS<2
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Remember the top-level container
+ tmp = fragment.firstChild;
+
+ // Ensure the created nodes are orphaned (#12392)
+ tmp.textContent = "";
+ }
+ }
+ }
+
+ // Remove wrapper from fragment
+ fragment.textContent = "";
+
+ i = 0;
+ while ( ( elem = nodes[ i++ ] ) ) {
+
+ // Skip elements already in the context collection (trac-4087)
+ if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+ if ( ignored ) {
+ ignored.push( elem );
+ }
+ continue;
+ }
+
+ contains = jQuery.contains( elem.ownerDocument, elem );
+
+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( contains ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( ( elem = tmp[ j++ ] ) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ return fragment;
+}
+
+
+( function() {
var fragment = document.createDocumentFragment(),
div = fragment.appendChild( document.createElement( "div" ) ),
input = document.createElement( "input" );
- // Support: Safari<=5.1
+ // Support: Android 4.0-4.3, Safari<=5.1
// Check state lost if the name is set (#11217)
// Support: Windows Web Apps (WWA)
// `name` and `type` must use .setAttribute for WWA (#14901)
@@ -4044,19 +4414,13 @@ var rcheckableType = (/^(?:checkbox|radio)$/i);
// Make sure textarea (and checkbox) defaultValue is properly cloned
div.innerHTML = "";
support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
-})();
-var strundefined = typeof undefined;
-
-
-
-support.focusinBubbles = "onfocusin" in window;
+} )();
var
rkeyEvent = /^key/,
- rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
- rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
- rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+ rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
function returnTrue() {
return true;
@@ -4066,12 +4430,75 @@ function returnFalse() {
return false;
}
+// Support: IE9
+// See #13393 for more info
function safeActiveElement() {
try {
return document.activeElement;
} catch ( err ) { }
}
+function on( elem, types, selector, data, fn, one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ on( elem, type, selector, data, types[ type ], one );
+ }
+ return elem;
+ }
+
+ if ( data == null && fn == null ) {
+
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return elem.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ } );
+}
+
/*
* Helper functions for managing events -- not part of the public interface.
* Props to Dean Edwards' addEvent library for many of the ideas.
@@ -4085,7 +4512,7 @@ jQuery.event = {
var handleObjIn, eventHandle, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
- elemData = data_priv.get( elem );
+ elemData = dataPriv.get( elem );
// Don't attach events to noData or text/comment nodes (but allow plain objects)
if ( !elemData ) {
@@ -4105,14 +4532,15 @@ jQuery.event = {
}
// Init the element's event structure and main handler, if this is the first
- if ( !(events = elemData.events) ) {
+ if ( !( events = elemData.events ) ) {
events = elemData.events = {};
}
- if ( !(eventHandle = elemData.handle) ) {
+ if ( !( eventHandle = elemData.handle ) ) {
eventHandle = elemData.handle = function( e ) {
+
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
- return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
+ return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
};
}
@@ -4121,9 +4549,9 @@ jQuery.event = {
types = ( types || "" ).match( rnotwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
- tmp = rtypenamespace.exec( types[t] ) || [];
- type = origType = tmp[1];
- namespaces = ( tmp[2] || "" ).split( "." ).sort();
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
@@ -4140,7 +4568,7 @@ jQuery.event = {
special = jQuery.event.special[ type ] || {};
// handleObj is passed to all event handlers
- handleObj = jQuery.extend({
+ handleObj = jQuery.extend( {
type: type,
origType: origType,
data: data,
@@ -4148,18 +4576,20 @@ jQuery.event = {
guid: handler.guid,
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
- namespace: namespaces.join(".")
+ namespace: namespaces.join( "." )
}, handleObjIn );
// Init the event handler queue if we're the first
- if ( !(handlers = events[ type ]) ) {
+ if ( !( handlers = events[ type ] ) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
// Only use addEventListener if the special events handler returns false
- if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ if ( !special.setup ||
+ special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
if ( elem.addEventListener ) {
- elem.addEventListener( type, eventHandle, false );
+ elem.addEventListener( type, eventHandle );
}
}
}
@@ -4191,9 +4621,9 @@ jQuery.event = {
var j, origCount, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
- elemData = data_priv.hasData( elem ) && data_priv.get( elem );
+ elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
- if ( !elemData || !(events = elemData.events) ) {
+ if ( !elemData || !( events = elemData.events ) ) {
return;
}
@@ -4201,9 +4631,9 @@ jQuery.event = {
types = ( types || "" ).match( rnotwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
- tmp = rtypenamespace.exec( types[t] ) || [];
- type = origType = tmp[1];
- namespaces = ( tmp[2] || "" ).split( "." ).sort();
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
// Unbind all events (on this namespace, if provided) for the element
if ( !type ) {
@@ -4216,7 +4646,8 @@ jQuery.event = {
special = jQuery.event.special[ type ] || {};
type = ( selector ? special.delegateType : special.bindType ) || type;
handlers = events[ type ] || [];
- tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+ tmp = tmp[ 2 ] &&
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
// Remove matching events
origCount = j = handlers.length;
@@ -4226,7 +4657,8 @@ jQuery.event = {
if ( ( mappedTypes || origType === handleObj.origType ) &&
( !handler || handler.guid === handleObj.guid ) &&
( !tmp || tmp.test( handleObj.namespace ) ) &&
- ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ ( !selector || selector === handleObj.selector ||
+ selector === "**" && handleObj.selector ) ) {
handlers.splice( j, 1 );
if ( handleObj.selector ) {
@@ -4241,7 +4673,9 @@ jQuery.event = {
// Remove generic event handler if we removed something and no more handlers exist
// (avoids potential for endless recursion during removal of special event handlers)
if ( origCount && !handlers.length ) {
- if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ if ( !special.teardown ||
+ special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
jQuery.removeEvent( elem, type, elemData.handle );
}
@@ -4249,145 +4683,12 @@ jQuery.event = {
}
}
- // Remove the expando if it's no longer used
+ // Remove data and the expando if it's no longer used
if ( jQuery.isEmptyObject( events ) ) {
- delete elemData.handle;
- data_priv.remove( elem, "events" );
+ dataPriv.remove( elem, "handle events" );
}
},
- trigger: function( event, data, elem, onlyHandlers ) {
-
- var i, cur, tmp, bubbleType, ontype, handle, special,
- eventPath = [ elem || document ],
- type = hasOwn.call( event, "type" ) ? event.type : event,
- namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
-
- cur = tmp = elem = elem || document;
-
- // Don't do events on text and comment nodes
- if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
- return;
- }
-
- // focus/blur morphs to focusin/out; ensure we're not firing them right now
- if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
- return;
- }
-
- if ( type.indexOf(".") >= 0 ) {
- // Namespaced trigger; create a regexp to match event type in handle()
- namespaces = type.split(".");
- type = namespaces.shift();
- namespaces.sort();
- }
- ontype = type.indexOf(":") < 0 && "on" + type;
-
- // Caller can pass in a jQuery.Event object, Object, or just an event type string
- event = event[ jQuery.expando ] ?
- event :
- new jQuery.Event( type, typeof event === "object" && event );
-
- // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
- event.isTrigger = onlyHandlers ? 2 : 3;
- event.namespace = namespaces.join(".");
- event.namespace_re = event.namespace ?
- new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
- null;
-
- // Clean up the event in case it is being reused
- event.result = undefined;
- if ( !event.target ) {
- event.target = elem;
- }
-
- // Clone any incoming data and prepend the event, creating the handler arg list
- data = data == null ?
- [ event ] :
- jQuery.makeArray( data, [ event ] );
-
- // Allow special events to draw outside the lines
- special = jQuery.event.special[ type ] || {};
- if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
- return;
- }
-
- // Determine event propagation path in advance, per W3C events spec (#9951)
- // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
- if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
-
- bubbleType = special.delegateType || type;
- if ( !rfocusMorph.test( bubbleType + type ) ) {
- cur = cur.parentNode;
- }
- for ( ; cur; cur = cur.parentNode ) {
- eventPath.push( cur );
- tmp = cur;
- }
-
- // Only add window if we got to document (e.g., not plain obj or detached DOM)
- if ( tmp === (elem.ownerDocument || document) ) {
- eventPath.push( tmp.defaultView || tmp.parentWindow || window );
- }
- }
-
- // Fire handlers on the event path
- i = 0;
- while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
-
- event.type = i > 1 ?
- bubbleType :
- special.bindType || type;
-
- // jQuery handler
- handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
- if ( handle ) {
- handle.apply( cur, data );
- }
-
- // Native handler
- handle = ontype && cur[ ontype ];
- if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
- event.result = handle.apply( cur, data );
- if ( event.result === false ) {
- event.preventDefault();
- }
- }
- }
- event.type = type;
-
- // If nobody prevented the default action, do it now
- if ( !onlyHandlers && !event.isDefaultPrevented() ) {
-
- if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
- jQuery.acceptData( elem ) ) {
-
- // Call a native DOM method on the target with the same name name as the event.
- // Don't do default actions on window, that's where global variables be (#6170)
- if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
-
- // Don't re-trigger an onFOO event when we call its FOO() method
- tmp = elem[ ontype ];
-
- if ( tmp ) {
- elem[ ontype ] = null;
- }
-
- // Prevent re-triggering of the same event, since we already bubbled it above
- jQuery.event.triggered = type;
- elem[ type ]();
- jQuery.event.triggered = undefined;
-
- if ( tmp ) {
- elem[ ontype ] = tmp;
- }
- }
- }
- }
-
- return event.result;
- },
-
dispatch: function( event ) {
// Make a writable jQuery.Event from the native event object
@@ -4396,11 +4697,11 @@ jQuery.event = {
var i, j, ret, matched, handleObj,
handlerQueue = [],
args = slice.call( arguments ),
- handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+ handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
special = jQuery.event.special[ event.type ] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
- args[0] = event;
+ args[ 0 ] = event;
event.delegateTarget = this;
// Call the preDispatch hook for the mapped type, and let it bail if desired
@@ -4413,24 +4714,25 @@ jQuery.event = {
// Run delegates first; they may want to stop propagation beneath us
i = 0;
- while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
event.currentTarget = matched.elem;
j = 0;
- while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+ while ( ( handleObj = matched.handlers[ j++ ] ) &&
+ !event.isImmediatePropagationStopped() ) {
// Triggered event must either 1) have no namespace, or 2) have namespace(s)
// a subset or equal to those in the bound event (both can have no namespace).
- if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+ if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
event.handleObj = handleObj;
event.data = handleObj.data;
- ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
- .apply( matched.elem, args );
+ ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+ handleObj.handler ).apply( matched.elem, args );
if ( ret !== undefined ) {
- if ( (event.result = ret) === false ) {
+ if ( ( event.result = ret ) === false ) {
event.preventDefault();
event.stopPropagation();
}
@@ -4453,15 +4755,20 @@ jQuery.event = {
delegateCount = handlers.delegateCount,
cur = event.target;
+ // Support (at least): Chrome, IE9
// Find delegate handlers
// Black-hole SVG instance trees (#13180)
- // Avoid non-left-click bubbling in Firefox (#3861)
- if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
+ //
+ // Support: Firefox<=42+
+ // Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
+ if ( delegateCount && cur.nodeType &&
+ ( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {
for ( ; cur !== this; cur = cur.parentNode || this ) {
+ // Don't check non-elements (#13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
- if ( cur.disabled !== true || event.type !== "click" ) {
+ if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
matches = [];
for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ];
@@ -4471,7 +4778,7 @@ jQuery.event = {
if ( matches[ sel ] === undefined ) {
matches[ sel ] = handleObj.needsContext ?
- jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery( sel, this ).index( cur ) > -1 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
if ( matches[ sel ] ) {
@@ -4479,7 +4786,7 @@ jQuery.event = {
}
}
if ( matches.length ) {
- handlerQueue.push({ elem: cur, handlers: matches });
+ handlerQueue.push( { elem: cur, handlers: matches } );
}
}
}
@@ -4487,19 +4794,20 @@ jQuery.event = {
// Add the remaining (directly-bound) handlers
if ( delegateCount < handlers.length ) {
- handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+ handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
}
return handlerQueue;
},
// Includes some event props shared by KeyEvent and MouseEvent
- props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+ props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
+ "metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ),
fixHooks: {},
keyHooks: {
- props: "char charCode key keyCode".split(" "),
+ props: "char charCode key keyCode".split( " " ),
filter: function( event, original ) {
// Add which for key events
@@ -4512,7 +4820,8 @@ jQuery.event = {
},
mouseHooks: {
- props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ props: ( "button buttons clientX clientY offsetX offsetY pageX pageY " +
+ "screenX screenY toElement" ).split( " " ),
filter: function( event, original ) {
var eventDoc, doc, body,
button = original.button;
@@ -4523,8 +4832,12 @@ jQuery.event = {
doc = eventDoc.documentElement;
body = eventDoc.body;
- event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
- event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ event.pageX = original.clientX +
+ ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
+ ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY +
+ ( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
+ ( doc && doc.clientTop || body && body.clientTop || 0 );
}
// Add which for click: 1 === left; 2 === middle; 3 === right
@@ -4581,10 +4894,12 @@ jQuery.event = {
special: {
load: {
+
// Prevent triggered image.load events from bubbling to window.load
noBubble: true
},
focus: {
+
// Fire native event if possible so blur/focus sequence is correct
trigger: function() {
if ( this !== safeActiveElement() && this.focus ) {
@@ -4604,6 +4919,7 @@ jQuery.event = {
delegateType: "focusout"
},
click: {
+
// For checkbox, fire native event so checked state will be right
trigger: function() {
if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
@@ -4628,41 +4944,21 @@ jQuery.event = {
}
}
}
- },
-
- simulate: function( type, elem, event, bubble ) {
- // Piggyback on a donor event to simulate a different one.
- // Fake originalEvent to avoid donor's stopPropagation, but if the
- // simulated event prevents default then we do the same on the donor.
- var e = jQuery.extend(
- new jQuery.Event(),
- event,
- {
- type: type,
- isSimulated: true,
- originalEvent: {}
- }
- );
- if ( bubble ) {
- jQuery.event.trigger( e, null, elem );
- } else {
- jQuery.event.dispatch.call( elem, e );
- }
- if ( e.isDefaultPrevented() ) {
- event.preventDefault();
- }
}
};
jQuery.removeEvent = function( elem, type, handle ) {
+
+ // This "if" is needed for plain objects
if ( elem.removeEventListener ) {
- elem.removeEventListener( type, handle, false );
+ elem.removeEventListener( type, handle );
}
};
jQuery.Event = function( src, props ) {
+
// Allow instantiation without the 'new' keyword
- if ( !(this instanceof jQuery.Event) ) {
+ if ( !( this instanceof jQuery.Event ) ) {
return new jQuery.Event( src, props );
}
@@ -4675,6 +4971,7 @@ jQuery.Event = function( src, props ) {
// by a handler lower down the tree; reflect the correct value.
this.isDefaultPrevented = src.defaultPrevented ||
src.defaultPrevented === undefined &&
+
// Support: Android<4.0
src.returnValue === false ?
returnTrue :
@@ -4700,6 +4997,7 @@ jQuery.Event = function( src, props ) {
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
+ constructor: jQuery.Event,
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse,
@@ -4709,7 +5007,7 @@ jQuery.Event.prototype = {
this.isDefaultPrevented = returnTrue;
- if ( e && e.preventDefault ) {
+ if ( e ) {
e.preventDefault();
}
},
@@ -4718,7 +5016,7 @@ jQuery.Event.prototype = {
this.isPropagationStopped = returnTrue;
- if ( e && e.stopPropagation ) {
+ if ( e ) {
e.stopPropagation();
}
},
@@ -4727,7 +5025,7 @@ jQuery.Event.prototype = {
this.isImmediatePropagationStopped = returnTrue;
- if ( e && e.stopImmediatePropagation ) {
+ if ( e ) {
e.stopImmediatePropagation();
}
@@ -4736,8 +5034,14 @@ jQuery.Event.prototype = {
};
// Create mouseenter/leave events using mouseover/out and event-time checks
-// Support: Chrome 15+
-jQuery.each({
+// so that event delegation works in jQuery.
+// Do the same for pointerenter/pointerleave and pointerover/pointerout
+//
+// Support: Safari 7 only
+// Safari sends mouseenter too often; see:
+// https://code.google.com/p/chromium/issues/detail?id=470258
+// for the description of the bug (it existed in older Chrome versions as well).
+jQuery.each( {
mouseenter: "mouseover",
mouseleave: "mouseout",
pointerenter: "pointerover",
@@ -4753,9 +5057,9 @@ jQuery.each({
related = event.relatedTarget,
handleObj = event.handleObj;
- // For mousenter/leave call the handler if related is outside the target.
+ // For mouseenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
- if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = fix;
@@ -4763,115 +5067,32 @@ jQuery.each({
return ret;
}
};
-});
+} );
-// Support: Firefox, Chrome, Safari
-// Create "bubbling" focus and blur events
-if ( !support.focusinBubbles ) {
- jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
-
- // Attach a single capturing handler on the document while someone wants focusin/focusout
- var handler = function( event ) {
- jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
- };
-
- jQuery.event.special[ fix ] = {
- setup: function() {
- var doc = this.ownerDocument || this,
- attaches = data_priv.access( doc, fix );
-
- if ( !attaches ) {
- doc.addEventListener( orig, handler, true );
- }
- data_priv.access( doc, fix, ( attaches || 0 ) + 1 );
- },
- teardown: function() {
- var doc = this.ownerDocument || this,
- attaches = data_priv.access( doc, fix ) - 1;
-
- if ( !attaches ) {
- doc.removeEventListener( orig, handler, true );
- data_priv.remove( doc, fix );
-
- } else {
- data_priv.access( doc, fix, attaches );
- }
- }
- };
- });
-}
-
-jQuery.fn.extend({
-
- on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
- var origFn, type;
-
- // Types can be a map of types/handlers
- if ( typeof types === "object" ) {
- // ( types-Object, selector, data )
- if ( typeof selector !== "string" ) {
- // ( types-Object, data )
- data = data || selector;
- selector = undefined;
- }
- for ( type in types ) {
- this.on( type, selector, data, types[ type ], one );
- }
- return this;
- }
-
- if ( data == null && fn == null ) {
- // ( types, fn )
- fn = selector;
- data = selector = undefined;
- } else if ( fn == null ) {
- if ( typeof selector === "string" ) {
- // ( types, selector, fn )
- fn = data;
- data = undefined;
- } else {
- // ( types, data, fn )
- fn = data;
- data = selector;
- selector = undefined;
- }
- }
- if ( fn === false ) {
- fn = returnFalse;
- } else if ( !fn ) {
- return this;
- }
-
- if ( one === 1 ) {
- origFn = fn;
- fn = function( event ) {
- // Can use an empty set, since event contains the info
- jQuery().off( event );
- return origFn.apply( this, arguments );
- };
- // Use same guid so caller can remove using origFn
- fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
- }
- return this.each( function() {
- jQuery.event.add( this, types, fn, data, selector );
- });
+jQuery.fn.extend( {
+ on: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn );
},
one: function( types, selector, data, fn ) {
- return this.on( types, selector, data, fn, 1 );
+ return on( this, types, selector, data, fn, 1 );
},
off: function( types, selector, fn ) {
var handleObj, type;
if ( types && types.preventDefault && types.handleObj ) {
+
// ( event ) dispatched jQuery.Event
handleObj = types.handleObj;
jQuery( types.delegateTarget ).off(
- handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.namespace ?
+ handleObj.origType + "." + handleObj.namespace :
+ handleObj.origType,
handleObj.selector,
handleObj.handler
);
return this;
}
if ( typeof types === "object" ) {
+
// ( types-object [, selector] )
for ( type in types ) {
this.off( type, selector, types[ type ] );
@@ -4879,6 +5100,7 @@ jQuery.fn.extend({
return this;
}
if ( selector === false || typeof selector === "function" ) {
+
// ( types [, fn] )
fn = selector;
selector = undefined;
@@ -4886,70 +5108,39 @@ jQuery.fn.extend({
if ( fn === false ) {
fn = returnFalse;
}
- return this.each(function() {
+ return this.each( function() {
jQuery.event.remove( this, types, fn, selector );
- });
- },
-
- trigger: function( type, data ) {
- return this.each(function() {
- jQuery.event.trigger( type, data, this );
- });
- },
- triggerHandler: function( type, data ) {
- var elem = this[0];
- if ( elem ) {
- return jQuery.event.trigger( type, data, elem, true );
- }
+ } );
}
-});
+} );
var
- rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
- rtagName = /<([\w:]+)/,
- rhtml = /<|?\w+;/,
- rnoInnerhtml = /<(?:script|style|link)/i,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
+
+ // Support: IE 10-11, Edge 10240+
+ // In IE/Edge using regex groups here causes severe slowdowns.
+ // See https://connect.microsoft.com/IE/feedback/details/1736512/
+ rnoInnerhtml = /