thednp / bootstrap.native

Bootstrap components build with Typescript
http://thednp.github.io/bootstrap.native/
MIT License
1.7k stars 177 forks source link

[Questions] - later added elements into the DOM: to be or not to be #102

Closed thednp closed 3 years ago

thednp commented 7 years ago

For those who work with DOM manipulations and use the library or have any experience with JavaScript driven dynamic content, please reply:

To the most frequent contributors (please forgive me for tagging you) @mathieuleclaire, @troyk, @mgiulio, @janpanschab, @RyanZim, @HelloGravity, @Jeff17Robbins, @dgt41, @oliseviche, @thewisenerd, @alzalabany, @kuus, @malexdev, @crcastle, @Aspie96, @CamWiseOwl, @antoligy, @zandaqo

AND anybody reading this page, please share your opinion.

kuus commented 7 years ago

Do you think we would need some tools and/or polyfills to enable our library to go LIVE (initialize proper BSN component for every new element added to the DOM) ?

I am not sure if bootstrap native uses event delegation but I seem to remember it does not, I think that could be enough. If I have to work with really dynamic pages I prefer using more robust frameworks.

About an IE8 polyfill, do you (or your scripts and tools) use mostly Element.prototype.appendChild, Element.prototype.innerHTML, both, or other methods?

Both

is out of topic but I would suggest to implement a modular es6 structure to the project (if I'll have time I'll try to work on that)

thednp commented 7 years ago

is out of topic but I would suggest to implement a modular es6 structure to the project (if I'll have time I'll try to work on that)

We will work on bootstrap.native for Bootstrap 4 with this in mind, based on the code of my future commits.

The future BSN release will feature bootstrap original events, 24Kb minimized, a ton of improvements and new features. It would be something to consider, especially if I manage to get it done with the new features I have in mind.

Jeff17Robbins commented 7 years ago

I would like a way to add one or more BSN component at "runtime" (after the page load time upgrading process has already initialized the elements originally on the page.) So, yes to question no1.

I noticed that Bootstrap 4 is apparently dropping IE8 and IE9 support. From https://v4-alpha.getbootstrap.com/migration/#browser-support:

  • Dropped IE8, IE9, and iOS 6 support. v4 is now only IE10+ and iOS 7+. For sites needing either of those, use v3.

So, one way we could support dynamic BSN initialization would be to make optional custom elements which know how to initialize automatically when they get attached to the DOM.

If you ignore the very cool "Shadow DOM" (see below), webcomponentsjs (https://github.com/WebComponents/webcomponentsjs) has a nice polyfill for custom elements that works nicely on IE10+. Chrome supports custom elements natively, and I believe Firefox and Safari do too.

The polyfill only needs to be loaded on browsers like IE10+ that don't support custom elements. The polyfill uses MutationObserver to notice new custom elements and upgrade them. Chrome notices via a built-in process, so you don't have the overhead of instrumenting the DOM with a MutationObserver, which makes this idea probably more performant and in the spirit of ditching jQuery and going native as much as possible.

We'd have to create custom elements, e.g. <bootstrap-modal> or whatever you want to call it, which would come with implementation in its "attached" callback that runs the right initialization JavaScript that would have happened at page loadtime had the element been present then. When I say "attached" callback, I'm referring to the webcomponents V0 spec. The spec has moved to V1 this article https://component.kitchen/blog/posts/our-experience-upgrading-web-components-from-shadow-domcustom-elements-v0-to-v1 discusses the changes.

But the polyfill for V1 isn't quite ready, but I'm imagining we could target (V1)[developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements], in which case the "attached" callback has been renamed to the "connectedCallback", a function on your custom element which gets called when you element is added to the DOM. Roughly the same idea with a new name. In that function, we'd initialize the modal. Any cleanup could happen in the "disconnectedCallback" (like removing event listeners.)

By publishing custom elements, we would make things like "modal" much more modular and object-oriented. By optionally using a polyfill on IE10+. we'd be clean on browsers supporting the V1 spec, and only take the MutationObserver hit on non-conformant browsers.

Note on ShadowDOM -- ShadowDOM is very cool because it creates a DOM island for your custom element that is essentially private, so that you can style it with your own CSS and not have to worry about other CSS rules bleeding through onto it. I don't think we need a ShadowDOM in our custom elements, because we want them to be styled by global Bootstrap and its theme. It is easier to create a custom element without ShadowDOM, and we can always add it later if we need to. But ShadowDOM is dicey in the polyfill, and there's been lots of churn around it in projects like Polymer, so I think we could ignore it for the time being.

-Jeff

thednp commented 7 years ago

I am working on an CustomEvent for BSN for BS3 update.bs.dom, works in IE8+

document.addEventListener('update.bs.dom', function(e){
  // e.target is document

  // e.relatedTarget is the new/removed element

  // OR

  // e.boundElements [Array] IE8 with a newly added/removed element and it's children 
  // this event is only triggered when appendChild/removeChild methods are used
}, false)

When the event fires, there could be a handler to apply a proper BSN component via DATA API or something. This is probably suitable for BSN for BS3, but what do you think?

Indeed the ShadowDOM spec sounds very interesting.

Aspie96 commented 7 years ago

I have been building a modified version of bootstrap.native that works even if you modify the DOM (without calling extra functions), but it is missing some elements.

Would it help if I published it on GitHub?

thednp commented 7 years ago

@Aspie96 I believe it's an IE9+ solution?

Aspie96 commented 7 years ago

I made it a while ago, but it should be IE8+. I haven't tested it on actual IE8, though (the latest version of IE contains a simulator of older versions, I used that one).

thednp commented 7 years ago

@Aspie96 please go for a fork of the current master and start applying your changes, I am curious if there could be something better than my current development.

Again, the BSN 1.5 series coming soon will have major code changes, for sure your version will not be mergeable with that, but at least we can all have an idea.

@Jeff17Robbins reading more into your input I can now say that everything you talk about is likely doable for the BSN for BS4, in combination with what @kuus was talking about. I am also interested in how you would want this to be implemented.

Aspie96 commented 7 years ago

Forking is exactly what I did, but I cannot publish the project right now since I am not at home.

It is actuallyn quite a bit different from yours, but it is fully based on yours.

As I said, some controls are missing.

Maybe, before pushing on GitHub, I will paste the code on PasteBin (to fix a few things before pushing, like… the manual XD). I hope you don't mind if I do so.

Thank you for using a free license such as MIT.

Jeff17Robbins commented 7 years ago

Here's a very crude, but working, example of packaging BSN Modal as a custom element. You need Chrome to see it run, because I didn't load the webcomponents polyfill.

https://jsfiddle.net/w1cmLc0g/

I call it 'crude' because it doesn't encapsulate everything about Modal, and it hasn't hidden the Modal's dialog contents in Shadow DOM, but it does define the toplevel aria and other attributes for you, showing how encapsulation can reduce user boilerplate.

<bsn-modal id="myModal">
  <div class="modal-dialog">
    <div class="modal-content">
      some content
    </div>
  </div>
</bsn-modal>

It takes care of that boilerplate plus instancing the BSN object in its connectedCallback:

  connectedCallback() {
    this.classList.add('modal');
    this.classList.add('fade');
    this.tabIndex = -1;
    this.setAttribute('role', 'dialog');
    this.setAttribute('aria-labelledby', 'myModalLabel');
    this.setAttribute('aria-hidden', 'true');

    this.modal = new Modal(this, {
      backdrop: 'true'
    });
    this.open = false;
  }

A fancier version would place the Modal's template in a <template> element, and use the <slot> element to parameterize the content of <bsn-modal>.

thednp commented 7 years ago

As much as I like your example, it's too outer space for me, maybe you can shed some light for me: do the customElements behave like they are under MutationObserver, or something to enable dynamic content...

NO NO, let me ask you this: is there anything you can explain to me who I just heard of customElements and MutationObserver what is BEST to do, WHERE is BEST to go forth?

Thanks :)

Jeff17Robbins commented 7 years ago

I don't understand your question, sorry. The beauty of a custom element is that it is a first class citizen of the browser. Rather than hijacking div s to make them be a modal, you can have a true bsn-modal element. Why is that outer space?

On Tue, Jan 17, 2017, 11:16 AM thednp notifications@github.com wrote:

As much as I like your example, it's too outer space for me, maybe you can shed some light for me: do the customElements behave like they are under MutationObserver, or something to enable dynamic content...

NO NO, let me ask you this: is there anything you can explain to me who I just heard of customElements and MutationObserver what is BEST to do, WHERE is BEST to go forth?

Thanks :)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/thednp/bootstrap.native/issues/102#issuecomment-273215993, or mute the thread https://github.com/notifications/unsubscribe-auth/ACAWhz0H1Qm-XtC4sRBtIScgmIvXmX7iks5rTOk_gaJpZM4Lj-Tp .

thednp commented 7 years ago

Why is that outer space?

It's a bit unusual for me, I still try to figure out ways to make components work in IE8+ and your examples blow me away so high... I never done this before, even if I knew there are some ways I can create custom elements, just I didn't investigate into that yet.

Anyways, we expect more people share their opinion in this topic, eventually we're getting somewhere.

Jeff17Robbins commented 7 years ago

Got it! Yeah, we've dropped everything below IE11, but I understand that not everyone can do that.

On Tue, Jan 17, 2017, 11:27 AM thednp notifications@github.com wrote:

Why is that outer space?

It's a bit unusual for me, I still try to figure out way to make components work in IE8+ and your examples blow me away so high...

Anyways, we expect more people share their opinion in this topic, eventually we're getting somewhere.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/thednp/bootstrap.native/issues/102#issuecomment-273219625, or mute the thread https://github.com/notifications/unsubscribe-auth/ACAWh-HOwXItzRET11r0lxpwNuA_9fwuks5rTOwAgaJpZM4Lj-Tp .

dgrammatiko commented 7 years ago

@Jeff17Robbins I improved your code bit, for multiple modals: http://codepen.io/dgt41/pen/LxbzQG

thednp commented 7 years ago

@Jeff17Robbins

So using custom elements for our library's components would make this library dynamic content enabled?

dgrammatiko commented 7 years ago

@thednp as I mentioned before, if I get some free time, I'm willing to create a Custom Elements fork of your library, @Jeff17Robbins is right CE are first class citizens for browsers and also the polyfill https://github.com/WebComponents/webcomponentsjs makes it compatible up to IE9(*)

Implementing your library as custom elements should be on your radar for BS4...

Jeff17Robbins commented 7 years ago

Yes, because you get a life cycle callback ("connected callback") which gets called when the element is attached to the DOM. That is your hook to do whatever you need to make the element useful.

There is also a "disconnectedCallback" when the element is removed from the DOM. Useful for cleanup.

On Tue, Jan 17, 2017, 11:37 AM thednp notifications@github.com wrote:

@Jeff17Robbins https://github.com/Jeff17Robbins

So using custom elements for our library's components would make this library dynamic content enabled?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/thednp/bootstrap.native/issues/102#issuecomment-273222446, or mute the thread https://github.com/notifications/unsubscribe-auth/ACAWhyFCI_AeU2oEfw9gSUpBKJLdV4EMks5rTO4tgaJpZM4Lj-Tp .

thednp commented 7 years ago

In my current code, I changed the way components get initialized via DATA API, I have a new utility for that.

Additionally, I've setup a bulkInitializeDataAPI utility, haven't tested yet but it should work; the utility could be used for BSN3, I guess, as I'm reading the above may not be required for BSN4.

Jeff17Robbins commented 7 years ago

@dgt41 I moved the dialog markup to templates. http://codepen.io/anon/pen/PWbXwr?editors=1010 Not necessarily better, but illustrates using the <template> tag.

thednp commented 7 years ago

@Jeff17Robbins, @dgt41 I've investigated how Bootstrap is handling events and more about the practice of delegating events to later added elements, they attach all event handlers to the document object and delegate to specific selectors. A practice jQuery developers themselves admitted it's a bad practice because that's too much of a burden on the document.

Instead, our way, we attach event handlers to each element on initialization, and if a modern browser detects you have deleted an element from the DOM, it will also clear the event handlers attached, which is a major plus.

Right now I have in mind a global click,scroll, etc handler attached to document but for all the components; this would save a huge ton of memory, not to mention the execution performance boost.

I am super curious about your ideas on that.

thednp commented 7 years ago

I just commited a new version, please do some testing everyone ;)

Jeff17Robbins commented 7 years ago

I updated my custom element Modal example to the latest cdn version. It still works. I haven't dug in to see how the new event code works, but thought I'd post the example if it helps.

http://codepen.io/anon/pen/GrOYoO?editors=1010

thednp commented 7 years ago

CDNjs is a bit behind, probably stuck in some semver related issue, here's the latest CDN https://cdn.jsdelivr.net/bootstrap.native/2.0.1/bootstrap-native.min.js

I changed your pen with this one and it's not working, you must check the updated documentation on Modal.

I made some changes, I hope you get the point of the changes http://codepen.io/thednp/pen/egeqJr

Jeff17Robbins commented 7 years ago

I'm confused. What is not working in http://codepen.io/anon/pen/GrOYoO?

thednp commented 7 years ago

That isn't the latest CDN. Should be 2.0.1.

Jeff17Robbins commented 7 years ago

This is the line from http://codepen.io/anon/pen/GrOYoO? that pulls in BSN: <script src='https://cdn.jsdelivr.net/bootstrap.native/2.0.1/bootstrap-native.js'></script>

Screenshot below. What isn't "2.0.1" about this? I'm confused because it says "2.0.1" in its path!

image

thednp commented 7 years ago

Sorry, your pen settings have the old CDN link, please check.

Jeff17Robbins commented 7 years ago

Oh. I am a jsfiddle user, and didn't know that the settings overrode my script. My bad! Before I go off to edit, could you please tell me what change(s) I need to know about? I'm asking because the example link you posted has a broken second button:

image

thednp commented 7 years ago

Now BSN 2.0 the Modal component will initialize only on data-toggle="modal" elements, which means buttons. So I don't know how to make the second button work as I don't understand ES6 just yet.

Jeff17Robbins commented 7 years ago

I think I am beginning to understand your changes.

  // MODAL DEFINITION
  // ===============
  var Modal = function(element, options) { // element is the is the modal

    // the triggering button element
    element = queryElement(element);

It seems that you are supporting the <button> either inside the modal DOM, or outside it. queryElement(...) supports both, but your call to it with only one argument forces it to search the whole document. This creates pressure to use unique id attributes, which we try to avoid as ids get in the way of instance-able components. Because ids have to be unique on the document.

So, it would be nice if the modal could find its button(s) inside itself, via modal.querySelector(...), instead of document.querySelector(...)

Maybe an option on Modal could indicate that the button is inside vs outside, but this is added work. Maybe the option could default to inside, so the modal could assume it is looking for its button(s) inside itself.

As a side note, I have a question about queryElement(...)

    queryElement = function (selector, parent) { // selector utility since 1.2.0
      var lookUp = parent ? parent : document;
      return typeof selector === 'object' ? selector : 
        (/^#/.test(selector) ? document.getElementById(selector.replace('#', '')) : lookUp.querySelector(selector));
    },

I don't understand why the code searches for the # character, seemingly only to call document.ElementById(...) instead of the relevant .querySelector(...). Why go to the CPU effort to find the #, if found strip it out, rather than simply call .querySelector(...) with the #. Presumably .querySelector(...) knows what # means and ought to be able to run some fast path to find it by id?

Or do you have some performance microbenchmark that proves otherwise? It seems like needlessly complex code, and forces a search of the whole document even if the user specified the parent argument.

thednp commented 7 years ago

Sorry var Modal = function(element, options) { // element is NOT the modal

I don't understand why the code searches for the # character, seemingly only to call document.ElementById(...) instead of the relevant .querySelector(...). Why go to the CPU effort to find the #, if found strip it out, rather than simply call .querySelector(...) with the #. Presumably .querySelector(...) knows what # means and ought to be able to run some fast path to find it by id?

Getting elements by ID is the fastest selector in ALL. If we find it, we use it.

Jeff17Robbins commented 7 years ago

Do you have proof that the combination of a regex, a string edit (to strip the #) and then byId actually IS faster? I doubt it is faster than handing querySelector the initial # selector.

thednp commented 7 years ago

OK @Jeff17Robbins queryElement is a selector utility, for a single element, but on your question please type in document.all in your console an hit Enter, which ones the parser is gonna look into first, the IDs or the others?

Now, please check the documentation on Modal, from top to bottom (also inspect the HTML of the examples), then we can have something to talk about, something we both have in mind.

Jeff17Robbins commented 7 years ago

I don't know what "document.all" has to do with how modern browsers implement querySelector. When querySelector sees a '#', it can INTERNALLY use any byID logic or indexing it might be maintaining. Why should if need the programmer to regex search for '#' etc...it seems so unlikely to me!

Meanwhile, I fixed my custom element wrapper for BSN 2.0: http://codepen.io/anon/pen/PWEPjv

While it works, I am not yet happy with it. My custom Modal element has to find its launching button, in order to instance the Modal class, and then that button has to have a data-target attribute to find the modal. It would be nice if only one thing had to point at the other thing.

thednp commented 7 years ago

OK, let me explain how that queryElement works and why. It will handle both DATA API initialization and JavaScript initialization, in multiple cases fashion:

Now, to get you happy, I can assure you that the it works now benefits you big time, with all components, all target elements (having a data-SOMTHING="component_specific_something") they will store the initialization object so you can access it anytime, even if it was initialized via DATA API.

As far as I understand with customElement stuff, you should work around a template and multiple triggers, when a trigger is clicked, you use its initialization to do a myButton.Modal.setContent('YOUR_HTML_CONTENT'). You have to change the way you think about it, it's the other way around: the button is the modal element, and the modal and your template the vehicle to deliver the content.

So a modal (and probably a template) can have multiple triggering elements, now how does that sound to you?

Jeff17Robbins commented 7 years ago

@thednp here's some evidence against your getElementById theory. getElementById, by itself, might be faster than querySelector, but once you add that regEx overhead, all bets are off! I wrote a simpler version of queryElement called queryElement2, and it is nearly 6X faster on my Windows/Chrome.

https://jsperf.com/queryelement-vs-queryselector

image

thednp commented 7 years ago

OK cool, I will do that your way asap ;)

Now, do you fully understand how the BSN modal works and why? Is there anything you would change?

Jeff17Robbins commented 7 years ago

@thednp , I am not following what you said:

As far as I understand with customElement stuff, you should work around a template and multiple triggers, when a trigger is clicked, you use its initialization to do a myButton.Modal.setContent('YOUR_HTML_CONTENT'). So a modal (and probably a template) can have multiple triggering elements, now how does that sound to you?

I think you are saying that an app might want to launch a modal via more than one UI element? But if that is true, why do I need to pass the triggering element to the Modal constructor?

Why does the Modal even have to know about the element(s) that trigger it? Or why couldn't that be optional, support a JavaScript-only mode?

thednp commented 7 years ago

Because each triggering button has/sets it's own content and holds the initialization, with settings and stuff.

Basically you can do a single <bsn-modal id="myWebSiteModal"> to do the job for everything and everyone.

Jeff17Robbins commented 7 years ago

I think I see where we are disconnected. I was picturing having multiple <bsn-modal> elements, one per use. It sounds like you are picturing associating each instance with the <button> instead.

There's probably a way to have it both ways. For my way, I could have the triggering <button> as part of my <bsn-modal>. Somehow.

thednp commented 7 years ago

Yes, but your way may not be suitable because both BSN and original jQuery plugins DON'T DO multiple modals at the same time, still why would you need it your way, practically?

Jeff17Robbins commented 7 years ago

I don't want to display multiple modals at once. But I want to "code" different modals, one for, say, an error condition, one for, say, a property sheet, etc... I'd like each potentially displayed modal to be a separate element instance.

thednp commented 7 years ago

You can still do that, you can code as many as you wish for each purpose and with as many templates you want, but you need to keep an eye on the triggering button and the link to it's modal.

I will look into the code and probably come with a better idea, ok?

thednp commented 7 years ago

@Jeff17Robbins you are not talking to me and I need feedback.

Also everyone else is welcomed to say their opinions. I really wish to have a community driven decision making.

Jeff17Robbins commented 7 years ago

It is nothing personal!

I guess I have nothing to add -- I don't like passing the "triggering element" to the modal constructor when using the JavaScript API. I'd rather let my button's click handler find the modal and call a method (e.g. toggle) on it.

But that's just my opinion.

dgrammatiko commented 7 years ago

@thednp @Jeff17Robbins check this: https://dgt41.github.io/bootstrap-components-as-custom-elements/

Jeff17Robbins commented 7 years ago

@dgt41 , nice!

The thing I don't like (sorry!) is that in the code you needed to do this:

triggerBtn = document.querySelector("button[data-target=\"#" + modal.id + "\"]");

and the HTML needed this image

It doesn't smell object-oriented that the implementation code of an object has to

  1. have a special id
  2. have code inside itself that then has to find a <button> that lives inside itself
  3. have HTML where the <button> also has to find something else via data-target=

Seems like a lot of extra glue for something that, somehow, ought to be more self-contained. In other words, I wish that I didn't need an id, and that the glue inside the object was simpler and wasn't also using an id.

Sorry if this seems too picky, but I honestly don't think we're at a clean implementation yet.

thednp commented 7 years ago

@Jeff17Robbins how do you link a trigger and a modal together? How would you write it so that it can satisfy us all? For instance I push content on a page via PHP, where do I get all these modals and their triggers linked together without an ID?

Jeff17Robbins commented 7 years ago

I'll give it some thought. I see that @dgt41 was able to avoid calling the Modal JavaScript API, but I guess that means that by putting the magic attributes on the element, some other code noticed and instanced a Modal as a side-effect.

I'd kinda like to instance the Modal inside the custom element constructor. And I'd like an option to NOT pass the trigger into that Modal. Then at least I'd only have a one-way pointer, from trigger to modal, and not the backpointer from modal to trigger.

Can the Modal API make the trigger argument optional?