diff --git a/can-jquery.js b/can-jquery.js index 7307783..8abff69 100644 --- a/can-jquery.js +++ b/can-jquery.js @@ -28,6 +28,15 @@ if ($) { // when using domEvents.dispatch/domEvents.trigger. var domDispatch = domEvents.dispatch; domEvents.dispatch = function(event, args) { + if(typeof event === "object" && !Object.getOwnPropertyDescriptor(event, "type")) { + // Some native events break jQuery dispatch by having non-enumerable + // type properties. + Object.defineProperty(event, "type", { + configurable: true, + enumerable: true, + value: event.type + }); + } if (!specialEvents[event] && !nativeDispatchEvents[event]) { $(this).trigger(event, args); } else { diff --git a/can-jquery_test.js b/can-jquery_test.js index 75a1518..b8b4590 100644 --- a/can-jquery_test.js +++ b/can-jquery_test.js @@ -14,6 +14,7 @@ var canEvent = require("can-event"); require("can-util/dom/events/inserted/inserted"); require("can-util/dom/events/removed/removed"); require("can-stache-bindings"); +require("can-jquery/data"); QUnit.module("can-jquery/legacy - can-controls", { setup: function() { @@ -446,3 +447,65 @@ QUnit.test("should call correct `removed` handler when one is removed", function fixture.append($el); $el.remove(); }); + +QUnit.module("can-jquery/legacy - data functions", { + setup: function() { + enableLegacyMode($); + this.$div = $("
").appendTo("#qunit-fixture"); + }, + teardown: function() { + disableLegacyMode(); + } +}); + +QUnit.test("data() compatibility with can-util/dom/data/", function() { + domData.set.call(this.$div[0], "foo", "bar"); + QUnit.equal(this.$div.data("foo"), "bar"); + + this.$div.data("foo", "baz"); + QUnit.equal(domData.get.call(this.$div[0], "foo"), "baz"); +}); + +QUnit.test("data() returns full data object from domData.get() with no arguments", function() { + domData.set.call(this.$div[0], "foo", "bar"); + QUnit.deepEqual(this.$div.data(), {"foo" : "bar"}); + + QUnit.deepEqual($.data(this.$div[0]), {"foo" : "bar"}); +}); + +QUnit.test("data() destructures objects before passing to domData.set", function() { + this.$div.data({"foo": "baz"}); + QUnit.equal(domData.get.call(this.$div[0], "foo"), "baz"); +}); + +QUnit.test("hasData() checks both jQuery data and domData", function() { + domData.set.call(this.$div[0], "foo", "bar"); + $.data(this.$div[0], "quux", "thud"); + QUnit.ok($.hasData(this.$div[0], "foo")); + QUnit.ok($.hasData(this.$div[0], "quux")); +}); + +QUnit.test("removeData() also calls domData.clean", function() { + domData.set.call(this.$div[0], "foo", "bar"); + domData.set.call(this.$div[0], "quux", "thud"); + + this.$div.removeData("foo"); + QUnit.ok(!domData.get.call(this.$div[0], "foo")); + QUnit.equal(domData.get.call(this.$div[0], "quux"), "thud"); + + this.$div.removeData(); // remove all remaining data; + QUnit.ok(!domData.get.call(this.$div[0], "quux")); + + // Repeat for static $.removeData + domData.set.call(this.$div[0], "foo", "bar"); + domData.set.call(this.$div[0], "quux", "thud"); + + $.removeData(this.$div[0], "foo"); + QUnit.ok(!domData.get.call(this.$div[0], "foo")); + QUnit.equal(domData.get.call(this.$div[0], "quux"), "thud"); + + $.removeData(this.$div[0]); // remove all remaining data; + QUnit.ok(!domData.get.call(this.$div[0], "quux")); +}); + + diff --git a/data.js b/data.js new file mode 100644 index 0000000..f647aea --- /dev/null +++ b/data.js @@ -0,0 +1,106 @@ +/* global module, require */ +var $ = require("can-jquery"); +var domData = require("can-util/dom/data/data"); +var each = require("can-util/js/each/each"); +var assign = require("can-util/js/assign/assign"); + +module.exports = $; + +var oldData = $.data; +var oldFnData = $.fn.data; +$.fn.data = function() { + var args = arguments, + ret = oldFnData.apply(this, arguments); + + if(arguments.length < 1) { + // get all (from the first element in the jQ) + assign(ret, domData.get.call(this[0])); + return ret; + } else if(arguments.length === 1 && typeof arguments[0] === "string") { + // get named property + if(ret != null) { + return ret; + } else { + return this.get().reduce(function(val, el) { + return val != null ? val : domData.get.apply(el, args); + }, null); + } + } else { + // set + this.each(function(i, el) { + if(typeof args[0] === "string") { + domData.set.apply(el, args); + } else { + each(args[0], function(val, key) { + domData.set.call(el, key, val); + }); + } + }); + return ret; + } +}; + +$.data = function() { + var elem = arguments[0], + args = [].slice.call(arguments, 1), + ret = oldData.apply(this, arguments); + + if(arguments.length < 2) { + // get all + assign(ret, domData.get.call(elem)); + return ret; + } else if(arguments.length === 2 && typeof arguments[1] === "string") { + // get named property + return ret != null ? ret : domData.get.apply(elem, args); + } else { + if(typeof args[0] === "string") { + domData.set.apply(elem, args); + } else { + each(args[0], function(val, key) { + domData.set.call(elem, key, val); + }); + } + return ret; + } +}; + +var oldHasData = $.hasData; +$.hasData = function() { + var elem = arguments[0], + args = [].slice.call(arguments, 1); + return oldHasData.apply(this, arguments) || domData.get.call(elem).hasOwnProperty(args[0]); +}; + +var oldRemoveData = $.removeData; +var oldFnRemoveData = $.fn.removeData; + +$.fn.removeData = function() { + var args = arguments, + ret = oldFnRemoveData.apply(this, arguments); + + this.each(function(i, el) { + if(typeof args[0] === "string") { + domData.clean.apply(el, args); + } else { + each(domData.get.call(el), function(val, key) { + domData.clean.call(el, key); + }); + } + }); + return ret; +}; + +$.removeData = function() { + var elem = arguments[0], + args = [].slice.call(arguments, 1), + ret = oldRemoveData.apply(this, arguments); + + if(typeof args[0] === "string") { + domData.clean.apply(elem, args); + } else { + each(domData.get.call(elem), function(val, key) { + domData.clean.call(elem, key); + }); + } + return ret; +}; diff --git a/docs/data.md b/docs/data.md new file mode 100644 index 0000000..9ba9771 --- /dev/null +++ b/docs/data.md @@ -0,0 +1,31 @@ +@module {jQuery} can-jquery/data can-jquery/data +@parent can-jquery.modules + +@description Integrates the data functions of CanJS and jQuery. + + +Importing can-jquery/data will return the [jQuery object](http://api.jquery.com/jquery/). It will also import all non-legacy [can-jquery] features for event binding and dispatch. + +```js +var $ = require("can-jquery/data"); +``` + +@body + +Importing `can-jquery/data` will also bring in [can-jquery], but also has the side effect of enabling jQuery data functions synchronizing with the [can-util/dom/data/data CanJS data store] and vice versa. + +This means that data set via the [can-util/dom/data/data.set `domData.set`] function will be available when calling [`jQuery.data()`](https://api.jquery.com/jquery.data/) or [`jQuery.fn.data()`](https://api.jquery.com/data/) as a getter; conversely, using `jQuery.data()` or `jQuery.fn.data()` as a setter and calling [can-util/dom/data/data.get `domData.get`] will again make the set data available on get. + +```js +var $ = require("can-jquery/data"); +var domData = require(""); + +var $el = $("
"); + +$el.data("foo", "bar"); +domData.get.call(el[0], "foo") // -> "bar" + +domData.set.call(el[0], { baz: "quux" }); +$el.data(); // -> { "foo": "bar", "baz": "quux" } +``` +