New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/object oriented #1036
Feature/object oriented #1036
Conversation
…to a new async instance method, `_main`
@limonte please review and let me know what you think, and then I have one more matter to discuss in regards to the |
Check out c4e7b70 and let me know if you want any more examples. (edit: examples of |
Here's another example of import Swal from 'sweetalert2'
export class CopyrightSwal extends Swal {
_main (params) {
return super._main({
...params,
footer: (params.footer ? params.footer + '<br/>' : '') + 'Copyright Rick Sanchez'
})
}
} or as a higher-order component, for better modularity and composibility: export function withCopyright (Swal) {
return class extends Swal {
_main (params) {
return super._main({
...params,
footer: (params.footer ? params.footer + '<br/>' : '') + 'Copyright Rick Sanchez'
})
}
}
}
const CopyrightSwal = withCopyright(Swal) Now lets try extending class FooterShorthandSwal extends Swal {
static argsToParams (args) {
if (typeof args[0] === 'string') { // if first arg is a string, make it the `footer` param...
return {
...Swal.argsToParams(args.slice(1)),
footer: args[3]
} else { // otherwise, just proxy to parent method
return Swal.argsToParams(args)
}
}
} Did you know, class MySwal extends Swal {
async _main (params) {
// do something with `params`
let result = await super._main(params)
// do something with `result`
return result
}
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the fundamental change which will make SweetAlert2 codebase much more maintainable and enjoyable to work with! You are the hero of my day @zenflow!
Just one nit to change:
src/instanceMethods/_main.js
Outdated
import { openPopup } from '../utils/openPopup' | ||
|
||
export function _main (userParams) { | ||
const ctor = this.constructor // dynamic reference to "the" (not necessarily our) constructor function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ctor
is quite a unique variable name, constructor
would be better IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I defined ctor
just to make the reference shorter, it's not really needed. If you want the ref to be constructor
then we might as well just make leave it this.constructor
which is probably better than either. Sound ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 1a49289
@limonte ok the part about Currently, the following interoperation of class ExtendedSwal extends Swal {}
const MixinSwal = ExtendedSwal.mixin({})
MixinSwal('foo') // <-- throws "Uncaught TypeError: Class constructor ExtendedSwal cannot be invoked without 'new'"
new MixinSwal('foo') // <-- throws the same error. the es super-constructor can not be `.apply`ed to the instance because `apply` can't signal the `new` keyword. I suggest fixing it by making a small breaking change. Luckily the If you would prefer to keep the ability to invoke mixin swals without the |
@limonte You ok with making that little breaking change to the |
I think we should change all the tests and documentation to say Can I go ahead with this? |
YES PLEASE! The whole plugin is starting to look so nice 💎 |
In 79aaea3 I just added a static e.g. import Swal from 'sweetalert2'
class MySwal extends Swal {/* ... */}
new MySwal('foo', 'bar') // <-- Standard lint error: "Do not use 'new' for side effects."
MySwal.fire('foo', 'bar') // <-- do this instead Maybe in the next major we can "not use 'new' for side effects" and make the constructor a pure function by deprecating simply calling |
I'm totally open to renaming |
Done in 7866171 edit: This was reverted and replaced with a better solution in PR #1043. You can now invoke a You must use |
Ready to merge IMO.. Next step will be to separate logic for distinct features into HOCs, ready to be extracted into separate packages in the next major...
|
I can't wait to integrate this change into sweetalert2-react-content |
@zenflow I can't understand what is the reason of gzipped dist size grown 0.5KB? In this PR there's just reorganization of the code, which shouldn't lead to the bigger dist. |
… to save bytes in final bundle
@limonte ahh maybe this is it.. this is the compiled var _extends$2 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function mixin(mixinParams) {
var Swal = this;
return function (_Swal) {
_inherits(MixinSwal, _Swal);
function MixinSwal() {
_classCallCheck(this, MixinSwal);
return _possibleConstructorReturn(this, (MixinSwal.__proto__ || Object.getPrototypeOf(MixinSwal)).apply(this, arguments));
}
_createClass(MixinSwal, [{
key: "_main",
value: function _main(params) {
return _get(MixinSwal.prototype.__proto__ || Object.getPrototypeOf(MixinSwal.prototype), "_main", this).call(this, _extends$2({}, mixinParams, params));
}
}]);
return MixinSwal;
}(Swal);
} |
This change is so good, I will merge it and make a new release right now! |
I remembered a pattern I learned from @substack, that lets you to create prototypical classes that can be instantiated without the
new
keyword..sweetalert2/src/sweetalert2.js
Lines 81 to 84 in a26d125
Using this we can enable a standard OO interface immediately, with almost full (see below) backwards compatibility.
So basically, you can still do
await swal('foo')
, but you can also doawait new swal('foo')
, and more importantly,class extends swal { _main(params) { ... }}
. The swal instance has 1 public property:params
and it isObject.freeze
d. It's public methods are all the methods insrc/instanceMethods.js
(which are also still available as actually-static methods, until the next major release), plusthen()
,catch()
andfinally()
to make it a thenable.Breaking change
The current changes as is will break any code depending on
swal('foo') instanceof Promise
beingtrue
, sinceswal
will no longer return aPromise
instance, butSwal
instance which is thenable. It will be treated the same as aPromise
in most cases, but there is the potential for this to break some expectations somewhere. I think it's probably negligible though, especially when considering the gain.Another approach would be to make
swal('foo') instanceof Promise
be true, by just setting up the prototype chain, but that would only make it look like an instance ofPromise
, and could lead to more confusing bugs than the alternative of being just a simple thenable.Benefits
Just to make explicit:
Extensions can be defined concisely (i.e. currently there's a lot of boilerplate, especially when there's accompanying tests), and can be typed properly (see sweetalert2/sweetalert2-react-content#15 (comment)).
...not to mention now we can think about what public instance methods/properties would make sense and be convenient. In the next major we can deprecate all the static methods that should be instance methods (and make them just instance methods). This change will also help in having a better organized API. Related: sweetalert2/sweetalert2.github.io#15 (comment)