Active-CSS / active-css

The epic event-driven browser language for UI with functionality in one-liner CSS. Over 100 incredible CSS commands for DOM manipulation, ajax, reactive variables, single-page application routing, and lots more. Could CSS be the JavaScript framework of the future?
https://activecss.org
Other
42 stars 7 forks source link

Add a scope(selector) to @component syntax to allow converting existing HTML into a component #205

Closed bob2517 closed 11 months ago

bob2517 commented 2 years ago

Components are quite powerful in ACSS, but it can get a bit complex when working with them. One of the limitations is that often I want to write isolated events and variables for a segment of HTML, but in order to do that I need to take the existing HTML out of a static page and render it manually within an ACSS component. This can be a bit annoying.

It could potentially be avoided by allowing existing pre-rendered HTML to get componented on-the-fly. This is just the start of an idea and it needs fleshing out a bit more, but I'm thinking along the lines of:

@scope (wrapper element) {
    img:click {
        /* do something on the img in the scope.
    }
}

The existing privacy options will then work like:

@scope (wrapper element) private {
    var: myPrivateCounter 0;
    img:click {
        /* do something on the img in the scope.
    }
}

The shadow option would work too, and just convert what's there into a shadow DOM. It will probably need some sort of componentOpen event so that if the wrapper element had a display of none then it could be manually shown or faded in or whatever. Although componentOpen may not be needed, because we've already got a draw event which handles that action adequately.

So it will work like the existing componext syntax, with nested components following the hierarchical component rules in ACSS, but it would just work with the HTML that is there rather than having to manually render stuff.

I think it's a case of writing some dummy code and comparing it to the existing component code and seeing what it's going to look like. Especially when handling a shadow DOM instance. It might be a game changer, it might not. But it should look more like CSS than the existing @component syntax because it won't have to contain HTML and CSS. This upgrade would work well in combination with the other issue about pre-rendering reactive variables. https://github.com/Active-CSS/active-css/issues/187

dragontheory commented 2 years ago

It might be a game changer

Umm... yes, please!

Feels like I'm stalking... lol

I'm not! I promise! I just believe you have something unique here that, as you said, could be a "game changer" and want to encourage you to keep going.

Did you check out those links for setting up donations? Free way to generate (some) income for this sort of thing. And easy way for fellow DEVs like myself to show our appreciation.

Have to read your component documentation again. As I mentioned before, my HTML is purposely super redundant for eventual componentization and am very interested in what you are thinking about this. When your componentization code in implemented, I am hoping my private and work project's code volume will be reduced by 75% or more. Super light weight and fast, both for small and scaled up world wide enterprise level projects, respectively.

Merry Christmas and happy new year BTW.

Was away taking care of the elderly and the sick. Glad to be back online.

Got to remember where I left off... Have a lot of catching up to do on your writings...

bob2517 commented 2 years ago

Merry Christmas dude! That's what happen when you watch the repo - you unfortunately get hit by my random ramblings!

Thank you for the feedback on this one. Yes - I think it could be one of the missing pieces to get right for components. Components have historically been tricky to make uncomplicated - not just for me but for all concerned with framework/browser dev. I have a feeling there are a couple more missing pieces to spot for the ACSS method, but we'll find out what's missing when this is implemented. This will probably be a release for 2.10.0.

I'm working on getting 2.9.0 out the door, with the new functionality and some key bug fixes and necessary refactorings. So you may get a lot of ramblings coming your way soon while I get that sorted. I'm aiming to get it done before the weekend if I can.

I did indeed check out the links. It's probably a good time to try and set something up for contributions while I've got a few days off. I'll look at it once 2.9.0 is a wrap though.

Thank you for your good work everywhere, and happy new year to you too!

bob2517 commented 2 years ago

Starting on this at the weekend, and very much looking forward to it. All the power of components for pre-rendered content. Auto-conversion to shadow DOM with a single option "shadow". Optionally encapsulated events and variables. I'm so glad I've already written all the code for all those things in a different form, otherwise it would have been a complete nightmare to spec up.

It would be rude to not include the server-side pre-rendered reactive variable issue along with this, and these should be good enough for a release on their own I think, unless anything else creeps in there, which it probably will. Oh yeah - the new "let" command for variables that are only available in a single event flow then disappear, like temporary counting variables. That's going to be in there too. But that should be it.

All backwards compatible of course. Can't say fairer than that, guv. Three for a penny!

bob2517 commented 2 years ago

Started on this and was thinking about nested components before I actually start doing anything. Although the other syntax for dynamic HTML components will work with this new syntax for pre-rendered sub-components, one option I can think of to allow nested pre-rendered inner-components is simply to allow nested @scope statements.

I would need to set up something in the core for parsing that kind of syntax. I don't have anything like that set up at the moment. Nesting like that only works for things like loops. We're talking about event selectors within @scope statements within event selectors.

The alternative would be to get some sort of "inside(outer component selector)" parameter added to a second inner @scope statement to specify which component an inner component would go into. That would be by far the simplest to implement, but not necessarily the nicest to work with, especially if the outer component is a shadow DOM component - it could get confusing trying to specify which selector to use to reference the outer component. Plus when declaring components on the top-level, that selector could potentially clash with something somewhere else on the page.

I'll do some dummy components and see what the code looks like for both options. It doesn't really matter in terms of getting it to work - I'm more concerned about getting the syntax which is most intuitive or most flexible. I could even do both syntaxes potentially, but I have a feeling that allowing a nested syntax is going to be the most helpful for self-contained components.

For importing components inside components, the existing component syntax is the most useful, as it allows the creation of HTML and CSS, and it's easy to nest components that way - the code editor on the docs site is a good example. The @scope statement is only for pre-rendered HTML, so the idea of nested sub-components only really works for that when all the HTML is already there and you want private areas within that HTML. Hence a nesting syntax can potentially come into play and be the most obvious to work with. On a large project that has lots of isolated components being rendered dynamically, the existing component syntax is the most flexible, and you don't get the chance of inconsistent HTML between components, because it all gets rendered on-the-fly.

I'll get the top-level @scope working first, and may release that first for 2.10.0, as it's by far the most common and useful aspect of this issue. Then work on the nested/inner pre-rendered component in a separate issue. That's the plan, unless the nested sub-components are more blatantly needed as part of this initial release.

dragontheory commented 2 years ago

The @scope statement is only for pre-rendered HTML

That sounds like a template to me.

Vue.js is structured like this...

<template>
  <div>HTML that goes in component template. 
</template>

As of Vue 3, there can be more than one root level template tag, each of which can have their own nested templates.

<template>
  <div>HTML that goes in component template. 
</template>
<template>
  <div>HTML that goes in component template. 
</template>

The template tag does not show up in the rendered HTML. Only the HTML that is inside each template.

I believe Angular is similar.

bob2517 commented 2 years ago

Yup - that's right. All web component methods, standard and non-standard (and there are loads of them) are basically the same concept of encapsulated and re-usable code. Some are easier to use than others. ACSS supports standard web components, and there are methods of setting up shadow DOM components in ACSS. The component methods in ACSS do the job, but the goal is to make things even simpler, and to allow developers to want to frequently create components because it actually makes things easier for them. Native web components have historically been tricky for developers to fully grasp and utilise effectively. They can be overly complex. As can React components.

The concept behind the @scope statement is that it will be able to turn any section of pre-rendered content into an isolated web component with isolated events and variables, in a single one-liner @scope statement. It doesn't require any special HTML - only browser standard HTML.

Like this component below. Imagine a very, very busy page - thousands of elements, and inside this page there is a single container with an id of "myContainer". There is a button, and some lis inside an ol. You've copied the code from some other site and you want to tweak it for this current site.

How do you add events to those items? That's where things can get complicated and you don't want to clash with the rest of the page in terms of selectors and variables.

So you could do this:

@scope #myContainer private {
    button:click {
        ajax: /mydata;
    }
    li:click {
        alert: "Hi there";
        @for n from 1 to 10 {
            render: "{n}";
        }
    }
}

That example would convert everything under #myContainer into a private component. You don't need to worry about the rest of the DOM. So you can just type "button:click", even if the rest of the DOM has buttons. You can just type "li:click", even if there are li tags elsewhere on the page. Even the {n} variable is isolated to that component and doesn't affect anything else outside the component. Everything is isolated. You don't have to apply outer CSS selection to the component event selectors to reference them - you just use "button", "li", or whatever is actually in the component. Multiple developers could work on the same page, and give their components to some bloke to set up container IDs, and these components would just work.

It's a really simple syntax and builds on what is in ACSS already. This method allows you to create isolated web components without having to know very much about components, and without doing anything special with your HTML.

And if you want to get fancy and want to isolate your HTML from the rest of the page too, you can turn the whole section into a standard shadow DOM just by doing this:

@scope #myContainer private shadow {
    button:click {
        ajax: /mydata;
    }
    li:click {
        alert: "Hi there";
        @for n from 1 to 10 {
            render: "{n}";
        }
    }
}

There's literally nothing else you would need to do. It's a really simple component technique, with no extraneous code. It goes straight to the point. Just by using the @scope statement, you can remove all the outer CSS selector references or even the necessity to use ids. LIke in the example above, you don't need to put an id on the button or reference the button like ".sectionOne .innerSection button:first-of-type", you can just type "button".

PS. The template tag is actually a standard browser tag. I use it to store pages for the offline docs site. Everything is stored in one massive HTML file, and each page is stored in a template tag. ACSS can already handle template tags. As I said, there are loads of component technologies - as many ways as there are frameworks. The template tag just allows you to place HTML in there so you can use it later on - it's hidden until it is used somewhere else on the page. It doesn't do much on it's own.

bob2517 commented 2 years ago

But this is just a method for already pre-rendered HTML. There is already an different and effective way to create components with on-the-fly HTML in ACSS.

This idea came about through using Laravel and the need to turn existing HTML into isolated event components with their own variables, without having to create the HTML on the front-end and pull it out of Laravel.

bob2517 commented 2 years ago

The only real difference between the @scope statement and the @component statement is ACSS, is that with @components you create the HTML on-the-fly, and @scope uses already existing HTML that is on the page.

That's really the only difference. The syntax is otherwise the same. You get more events with @component, like "componentOpen", and you write your HTML inside the @component too. But generally it's going to be the same syntax and rules. There's just less typing and less mucking about to set things up with this method if you've already rendered your HTML on the back-end.

bob2517 commented 2 years ago

I've written hundreds of components in ACSS now, and I can honestly say that just the idea of having to write a component in any other language would put me off programming altogether. I just wouldn't waste my time. And that goes for standard native web components too. I would literally change trade and become a gardener if I was forced into building JS/React/Vue/Alpine components, or become an IT manager or something. In fact one of my good friends moved out of programming exactly for this reason. It was too much effort to keep up with the framework component evolutions that companies were beginning to insist on, so he moved away from programming altogether and went into management.

That's just my opinion though - other people have different opinions. But for me, I like the intuitive fluidity of ACSS and there's less typing, and there's the fact that I know it really well, of course.

bob2517 commented 2 years ago

In fact, this @scope syntax would greatly simplify regular CSS.

You could get true isolation of CSS without using shadow DOMs, if this was implemented into CSS. You could just have an option in the statement as to whether it inherits the outer CSS rules by default.

You wouldn't have to rely on the points system inherit in the CSS hierarchy. It could actually simplify CSS for a lot of experienced developers who probably don't even know about the points system in CSS, which is probably why CSS sometimes is considered difficult to work with.

You could just scope an area, and apply CSS just to that area without specifying so much with the selectors.

I think I may have inadvertently stumbled onto something good with this @scope statement. We'll see!

bob2517 commented 2 years ago

Thinking about this some more - the problem with shadow DOMs is that you get a completely fresh DOM model to work with. No CSS is inherited at all. This is fine for websites that have completed isolated and separate sections, but not so practical for the average website, which normally would have shared CSS. But with the @scope method you could get a seamless merge of content but with the benefit of isolated elements to reference.

But that's really for the browser people to spec up. If it works well with events, then it might be worth proposing it to the CSS team. But it's just an idea - it might be a terrible one and it's a bit too early to make a judgement call on it.

dragontheory commented 2 years ago

But with the @scope method you could get a seamless merge of content but with the benefit of isolated elements to reference.

Would the upcoming CSS Modules be similar? https://developer.adobe.com/commerce/pwa-studio/guides/general-concepts/css-modules/

bob2517 commented 2 years ago

I think that's more of a JS or back-end tooling proposal.

There's another link here: https://devopedia.org/css-modules

dragontheory commented 2 years ago

I think that's more of a JS or back-end tooling proposal.

Ooops. Wrong link. lol. I have an average of 20 tabs open at any given time, all grouped on topics that I am currently studying. So I grabbed the wrong tab link!

I like the link you gave!

I am always looking for more resources/opportunities to learn. Here is another link that makes the distinction between "native CSS modules" and the popular open source project. https://css-tricks.com/css-modules-the-native-ones/

BTW, I have no affiliation with "CSS -Tricks" or Chris Coyier. They just seem to show up a lot with the topics I search I guess...

bob2517 commented 2 years ago

Ah. Yeah - I've never seen that before. That's pretty cool. Shadow DOMs have always been an all or nothing solution, and this looks like it's something in between, which is a good move.

The @scope statement is more of a programming tool than a styling tool. It scopes events and variables. But... when the CSS modules spec gets implemented, I can add an option to the existing load-style command which, when used inside of @scope or @component, will load the stylesheet and apply it only to that component. You can already load stylesheets with shadow DOM components, but the ability to add it to a non-shadow DOM component, or a @scope - a set of elements - would be really cool. You could really separate out your CSS if you wanted to then. You'd get a flicker while the CSS kicked in with @scope unless you hid the HTML first and then displayed it after @scope did its thing, but it would be perfectly useable.

So that would be really useful. Thanks for the link. When it is available in Chrome for real, I'll build it in - it will complement @scope rather than infringe upon it.

bob2517 commented 2 years ago

I'll reply to your other comment shortly - just got back from work and need to eat.

dragontheory commented 2 years ago

BTW, it seems that there is a way to accept sponsors in GitHub...

This little JS plugin has a sponsor page.

https://github.com/sponsors/nathancahill

Here is another more direct link about it. https://docs.github.com/en/sponsors/sponsoring-open-source-contributors/sponsoring-an-open-source-contributor

bob2517 commented 2 years ago

Funnily enough, I set up my github sponsor page at the weekend. Just waiting for it to go through now, it takes a few days I think...

dragontheory commented 2 years ago

Funnily enough, I set up my GitHub sponsor page at the weekend. Just waiting for it to go through now, it takes a few days I think...

Excellent!

Reading up on the fetch API and I just have no idea what I'm doing. And this still doesn't tell me how to get a count of the JSON objects or how to count them. https://css-tricks.com/using-fetch/

dragontheory commented 2 years ago

Funnily enough, I set up my GitHub sponsor page at the weekend. Just waiting for it to go through now, it takes a few days I think...

Another popular sponsorship thing other big names in DEV use is https://www.patreon.com. Not sure if there is a cost associated with it but most of the pro DEVs and YouTubers that I follow use it.

bob2517 commented 2 years ago

Funnily enough, I set up my GitHub sponsor page at the weekend. Just waiting for it to go through now, it takes a few days I think...

Excellent!

Reading up on the fetch API and I just have no idea what I'm doing. And this still doesn't tell me how to get a count of the JSON objects or how to count them. https://css-tricks.com/using-fetch/

Yeah - fetch doesn't bring anything more to the ajax table other than a fancy syntax.

You could just add together totalvisible and totalnotvisible, as you're looping those anyway and need to run than to get the total number of pages. I thought (JSON).length would work, but it doesn't always - I need to look at properties / variables freshly in ACSS, as they can be improved.

Meanwhile, you could just do "var: grandTotal {totalvisible} + {totalnotvisible};" or something like that. Does the same thing.

Or even just count in the loop itself - just "var: grandTotal 0;" in body:init, then "var: grandTotal++;" in the loop itself, outside of the completely-visible @ifs, just to count the lis as you loop them to work out if they are visible or not. Just do it to simply add up the lis as you loop. As I say, you need to do this loop anyway before you calculate how many pages to draw.

bob2517 commented 2 years ago

Funnily enough, I set up my GitHub sponsor page at the weekend. Just waiting for it to go through now, it takes a few days I think...

Another popular sponsorship thing other big names in DEV use is https://www.patreon.com. Not sure if there is a cost associated with it but most of the pro DEVs and YouTubers that I follow use it.

I'll check it out, thanks :)

bob2517 commented 2 years ago

Change to plan on this, I'm going to make things simpler and utilise the new @component syntax that is being set up.

Options in there for using HTML will be:

html-file(/myfile.html) - something like that
html(selector containing HTML already rendered)

/* and inside the component itself if neither of those are used: */
html {
}

I think that covers everything. We don't need the @scope statement at all by doing this. Boom!

bob2517 commented 2 years ago

Maybe selector() instead of html() to make it different to html { }... will look at that when I get onto this.

bob2517 commented 1 year ago

Gonna bite the bullet and add @scope next. Good to see it ended up in the CSS spec a few months after I mentioned it here in this issue. Now it's time to get the functionality into ACSS for events, so we can rid ourselves of the necessity to render private components on the fly when pre-rendering is a better option for performance...

bob2517 commented 1 year ago

It seems to only make sense to have private or strictlyPrivate options with @scope. Otherwise you're in a global scope - if the rules of components are to be followed.

But I think, for simplicities sake, I'm going to get it doing the same thing as rendered components. The logic that I will add with effectively attach the scoped events and variables to pre-rendered HTML, and just literally bypass the rendering steps. This should also keep the upgrade lean in the core. That's the theory anyway. Will aim to get this done by the end of the weekend.

bob2517 commented 1 year ago

A note on this for the docs as I'm progressing on it. There is going to be a general rendering performance hit if @scope is used. It should be used sparingly. Reason being is that I'm having to check every element at the point of render to see if it needs converting into a component at the point of render, and it needs to be done there in order to handle nested scopes sensibly. The hit has to happen there because if I don't do it at render time then I slow up the event flow by adding in the necessary event and variable scope checks dynamically. I'm hoping the hit won't be too bad to be noticeable - it's at the point of render, so the event flow speed shouldn't be affected. There won't be a hit if @scope isn't used in the config, so it won't affect devs who don't use it. I'll try and minimise the impact as much as possible.

There have been a couple of times when I've had to move HTML out of the back-end and into the front-end, just to get component-type functionality, and in the process had a noticeable hit in rendering speed as a result. So even though there is a hit, it may be a superior option to use @scope considering the alternatives. We're talking milliseconds, regardless.

bob2517 commented 1 year ago

Looking at my notes, earlier I was going to do this:

@component html(#myDivWrapper) private {
}

I actually think that is better, and will keep the core leaner as there isn't a bunch more stuff to code for parsing the config. But I'll do it like this, as I prefer the "scope" term, and I think it's less confusing:

@component scope(#myDivWrapper) private {
}
bob2517 commented 1 year ago

Update. Got these working offline:

@component scope(#specialSpans) privateEvents {
    span:click {
        add-class: .blue;
    }
}

@component scope(div:not(#specialSpans)) privateEvents {
    span:click {
        add-class: .black;
    }
}

on this HTML:

<div id="specialSpans">
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
</div>

<div>
    <span>5</span>
    <span>6</span>
    <span>7</span>
    <span>8</span>
</div>

Clicking on a span on the top row adds the blue class. Clicking on a span on the bottom row adds the black class. It only works in the top-level document scope at the moment.

So just need to tie in the scoping rules next, which I have a cunning plan for. Then maybe the shadow DOM thing, although I don't like the idea of HTML appearing and then disappearing and appearing in a shadow DOM - will look messy - may not bother with it. Then it's a case of testing a lot of scenarios and doing other checks on things. Going better than I thought it would though. No messy graphical transitions - it just silently attaches the component properties and leaves the HTML where it was when it was pre-rendered.

bob2517 commented 1 year ago

This is pretty much done I think now. Initial tests on var scoping are good, just need to run some more tests.

bob2517 commented 1 year ago

Committed to branch for further tests.

bob2517 commented 1 year ago

There's an issue with using a strictlyPrivate nested component - the event is bubbling up. The variables are staying contained within the strictlyPrivate component, but the event is repeating on the parent, and it shouldn't be doing that on a strictlyPrivate component.

bob2517 commented 1 year ago

Fix on branch for the strictlyPrivate bubbling issue. This was actually an error with components in general, but should now be fixed.

bob2517 commented 11 months ago

Closing pending release.