From 877d331ca420603c8aff11b4c69125b4a7493cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Thu, 22 Jun 2017 10:20:00 +0200 Subject: [PATCH 1/4] utils/fix-proto: conditionally polyfill support for `__proto__` --- addon/utils/fix-proto.js | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 addon/utils/fix-proto.js diff --git a/addon/utils/fix-proto.js b/addon/utils/fix-proto.js new file mode 100644 index 00000000..75c56edd --- /dev/null +++ b/addon/utils/fix-proto.js @@ -0,0 +1,76 @@ +/** + * @module Utils + * @private + */ + +/** + * Internet Explorer <= 10 has no support for `__proto__`. + * This conditional polyfill works around that, so that static methods like + * `reopen` may be used. There are some caveats though. + * + * For more information, please see: [Issue #436 (comment)][436] + * + * [436]: https://github.com/offirgolan/ember-light-table/issues/436#issuecomment-310138868 + * + * @class fixProto + */ + +/** + * Whether or not this environment supports `__proto__`. + * IE <= 10 is known to not support it- + * + * More information on `__proto__`: + * + * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/proto + * + * @const isProtoSupported + * @type {Boolean} + * @readOnly + */ +export const isProtoSupported = ({ __proto__: [] }) instanceof Array; + +/** + * Assigns all properties of `defaults` to `obj`, if they are not already + * defined on `obj`. This means that `obj` is mutated in place. + * + * Taken from: + * + * https://github.com/babel/babel/blob/64eafad472ebac6333671fff65a9669739e6cd88/packages/babel-helpers/src/helpers.js#L287-L299 + * + * @method defaults + * @param {Object} obj The object to assign the default properties to + * @param {Object} defaults The object that provides all default properties to + * be assigned + * @return {Object} The `obj` that was passed in + */ +export function defaults(obj, defaults) { + const keys = Object.getOwnPropertyNames(defaults); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = Object.getOwnPropertyDescriptor(defaults, key); + if (value && value.configurable && obj[key] === undefined) { + Object.defineProperty(obj, key, value); + } + } + return obj; +} + +/** + * Conditionally attempt to polyfill support for `__proto__` in environments + * that do not support it. + * + * If `__proto__` is not supported, this function assigns all properties and + * methods pf `Class.__proto__` to `Class` itself. + * + * @method fixProto + * @param {Function} Class The base class + * @return {Function} The `Class` that was passed in + */ +export default function fixProto(Class) { + if (isProtoSupported) { + return; + } + + // https://github.com/babel/babel/tree/64eafad472ebac6333671fff65a9669739e6cd88/packages/babel-plugin-transform-proto-to-assign#example + return defaults(Class, Class.__proto__); +} From 6df9e5a133697d81d5ecb4fb32a9b1be1ef3032a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Thu, 22 Jun 2017 10:21:04 +0200 Subject: [PATCH 2/4] classes/{Column,Row,Table}: polyfill `__proto__` with fixProto --- addon/classes/Column.js | 4 ++++ addon/classes/Row.js | 4 ++++ addon/classes/Table.js | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/addon/classes/Column.js b/addon/classes/Column.js index 5cdaa929..9514944b 100644 --- a/addon/classes/Column.js +++ b/addon/classes/Column.js @@ -1,4 +1,5 @@ import Ember from 'ember'; +import fixProto from 'ember-light-table/utils/fix-proto'; const { guidFor, @@ -344,3 +345,6 @@ export default class Column extends EmberObject.extend({ this.set('subColumns', subColumns); } } + +// https://github.com/offirgolan/ember-light-table/issues/436#issuecomment-310138868 +fixProto(Column); diff --git a/addon/classes/Row.js b/addon/classes/Row.js index c492c5c0..95847cce 100644 --- a/addon/classes/Row.js +++ b/addon/classes/Row.js @@ -1,4 +1,5 @@ import Ember from 'ember'; +import fixProto from 'ember-light-table/utils/fix-proto'; const { computed, @@ -100,3 +101,6 @@ export default class Row extends ObjectProxy.extend({ this.set('content', content); } } + +// https://github.com/offirgolan/ember-light-table/issues/436#issuecomment-310138868 +fixProto(Row); diff --git a/addon/classes/Table.js b/addon/classes/Table.js index 8cd8be49..f3db12c6 100644 --- a/addon/classes/Table.js +++ b/addon/classes/Table.js @@ -3,6 +3,7 @@ import Row from 'ember-light-table/classes/Row'; import Column from 'ember-light-table/classes/Column'; import SyncArrayProxy from 'ember-light-table/-private/sync-array-proxy'; import { mergeOptionsWithGlobals } from 'ember-light-table/-private/global-options'; +import fixProto from 'ember-light-table/utils/fix-proto'; const { get, @@ -425,3 +426,6 @@ export default class Table extends EmberObject.extend({ return columns.map((c) => Table.createColumn(c)); } } + +// https://github.com/offirgolan/ember-light-table/issues/436#issuecomment-310138868 +fixProto(Table); From e81db697369e045e78e4257fc034c3b0b17cac2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Fri, 23 Jun 2017 15:23:50 +0200 Subject: [PATCH 3/4] tests/fix-proto: ES6 classes extending Ember.Object should be reopenable --- tests/unit/utils/fix-proto-test.js | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/unit/utils/fix-proto-test.js diff --git a/tests/unit/utils/fix-proto-test.js b/tests/unit/utils/fix-proto-test.js new file mode 100644 index 00000000..fe187467 --- /dev/null +++ b/tests/unit/utils/fix-proto-test.js @@ -0,0 +1,35 @@ +import Ember from 'ember'; +import fixProto from 'ember-light-table/utils/fix-proto'; +import { module, test } from 'qunit'; + +const { + Object: EmberObject +} = Ember; + +module('Unit | Utility | fix proto'); + +/* + * Running this test in an environment that does not need the polyfill doesn't + * make any sense. The only relevent environments are IE <= 10. + */ + +test('ES6 classes that extend `Ember.Object` can be reopened after `fixProto` was used on them', function(assert) { + class DerivedClass extends EmberObject.extend() {} + + fixProto(DerivedClass); + + DerivedClass.reopenClass({ + someStaticMethod() { + return true; + } + }); + assert.ok(DerivedClass.someStaticMethod(), 'reopenClass works'); + + DerivedClass.reopen({ + someMethod() { + return true; + } + }); + let instance = new DerivedClass(); + assert.ok(instance.someMethod(), 'reopen works'); +}); From 23be76346e66f42f1c022803157683ef0c95bd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Fri, 23 Jun 2017 15:24:24 +0200 Subject: [PATCH 4/4] tests/classes/{Column,Row,Table}: assert that reopen and reopenClass are functions --- tests/unit/classes/column-test.js | 5 +++++ tests/unit/classes/row-test.js | 5 +++++ tests/unit/classes/table-test.js | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/tests/unit/classes/column-test.js b/tests/unit/classes/column-test.js index 0689329f..ae3b8398 100644 --- a/tests/unit/classes/column-test.js +++ b/tests/unit/classes/column-test.js @@ -29,6 +29,11 @@ test('create column - column instance', function(assert) { assert.equal(col2.label, 'Name'); }); +test('reopen colum', function(assert) { + assert.equal(typeof Column.reopen, 'function', 'reopen is a function'); + assert.equal(typeof Column.reopenClass, 'function', 'reopenClass is a function'); +}); + test('CP - isGroupColumn', function(assert) { let col = new Column(); assert.ok(col); diff --git a/tests/unit/classes/row-test.js b/tests/unit/classes/row-test.js index 8098f75d..8283f89d 100644 --- a/tests/unit/classes/row-test.js +++ b/tests/unit/classes/row-test.js @@ -19,3 +19,8 @@ test('create row - row instance', function(assert) { assert.equal(row.get('foo'), 'bar'); assert.equal(row2.get('foo'), 'bar'); }); + +test('reopen row', function(assert) { + assert.equal(typeof Row.reopen, 'function', 'reopen is a function'); + assert.equal(typeof Row.reopenClass, 'function', 'reopenClass is a function'); +}); diff --git a/tests/unit/classes/table-test.js b/tests/unit/classes/table-test.js index 8f9e53a0..2dcefe93 100644 --- a/tests/unit/classes/table-test.js +++ b/tests/unit/classes/table-test.js @@ -27,6 +27,11 @@ test('create table - with options', function(assert) { assert.ok(table.columns[0] instanceof Column); }); +test('reopen table', function(assert) { + assert.equal(typeof Table.reopen, 'function', 'reopen is a function'); + assert.equal(typeof Table.reopenClass, 'function', 'reopenClass is a function'); +}); + test('CP - visibleColumnGroups', function(assert) { let table = new Table(); let col = new Column();