What?
modal-closer
's targetObject
property is parentView.parentView.parentView
which places a pretty hard constraint on the layout of the modal -- no other views/components can be added to the layout in between the modal header/footer and the bs-modal
component or the close action will get sent to the wrong ancestor component.
Why?
Adding additional wrapping components to the layout? Why would somebody ever want to do such a thing? I'm glad you asked! :)
For reasons not really relevant to this issue, I am using a custom form component that performs validations using parsley.js, rather than bs-form
. Also, I prefer to align my generated HTML with what the browser expects whenever possible, as it makes various "off the beaten path" uses cases such as old-school mobile browsers, screen readers and accessibility more likely to function correctly. So, I want to lay out my modal something like this (leaving out a bunch of the details):
{{#bs-modal body=false footer=false}}
{{#parsley-form action=(action "submit")}}
{{#bs-modal-body}}
<input type="text"/>
{{/bs-modal-body}}
{{bs-modal-footer closeTitle="cancel" submitTitle="ok"}}
{{/parsley-form}}
{{/bs-modal}}
This way the form wraps both the body and the footer and I don't have to put a hidden <input type="submit"/>
or do custom key handling to support submit-form-on-enter.
That is my use case, but speaking more broadly, the restriction that you cannot wrap the modal header or footer in a component seems less than ideal.
How?
I have two proposals for how to address this, one that puts a teeny burden on the developer but is more Ember-onic, and one that is more similar to the current implementation in that it "just works".
My preference is option 1, but I'd be happy to put together a PR for either one.
Option 1
The targetObject
property on the modal-closer
is override-able, so if the template had access to the bs-modal
component, it could just pass it in, overriding the default. This would involve changing the bs-modal
's {{yield}}
to {{yield this}}
, and then any developer that wants to insert an extra component would just need to add as |modal|
to the {{#bs-modal}}
and targetObject=modal
to the {{bs-modal-footer}}
:
{{#bs-modal body=false footer=false as |moda|}}
{{#parsley-form action=(action "submit")}}
{{#bs-modal-body}}
<input type="text"/>
{{/bs-modal-body}}
{{bs-modal-footer closeTitle="cancel" submitTitle="ok" targetObject=modal}}
{{/parsley-form}}
{{/bs-modal}}
Option 2
This is a bit of a hack, but I believe would work. We could just make the targetObject
property on the modal-closer
a bit more intelligent. If we add an isBsModal: true
property to bs-modal
, then we could redefine modal-closer
's targetObject
to search up the tree like this:
targetObject: Ember.computed('parentView.parentView.parentView.parentView.parentView.parentView', function() {
let view = this.get('parentView');
while (view && !view.get('isBsModal')) {
view = view.get('parentView');
}
return view;
});
All the .parentView
s in the computed property dependency are for invalidation in case things are re-rendered such that the modal-closer
component persists, but the ancestor hierarchy changes. This would allow three additional wrapping components in between the modal-closer
and the bs-modal
-- obviously we could make it support more by adding more .parentView
s. It might also work to do it like this:
targetObject: null,
didInsertElement() {
this._super(...arguments);
let view = this.get('parentView');
while (view && !view.get('isBsModal')) {
view = view.get('parentView');
}
this.set('targetObject', view);
}
but I'd have to do a bit of research to make sure willClearRender
/didInsertElement
would reliably be called anytime the ancestor hierarchy changes.
So, yeah, this option is kind of hacky which is why I prefer option 1.