This is a cool module! I’ve been getting into Roact and the principle behind this is very similar to Roact (I believe Roact can also manage non-UI instances). What are the primary differences between this module and Roact?
Good question!
Roact is largely built around building UI and managing state; the idea with Roact is that you build a description of some UI in code, and pass that description to Roact (along with some game data). Roact then creates all the instances needed and automatically keeps everything up to date as your game data changes. It enforces a very specific and rigid workflow which is completely different to how UI development has traditionally worked on Roblox.
Components is a lot more flexible and general, but doesn’t provide a state management system. This makes Components much more widely applicable - you can use it for more than just creating UI, though it absolutely can just do UI if that’s all you need. However, as your game data changes, you’ll have to write the code to change the properties on your instances manually, like you normally do. Unlike Roact, Components tries to augment your existing workflow rather than replacing it entirely.
Cool creation!
How do you intend for this framework to deal with different “themes”? Is it the responsibility of each component to handle theming on their own? Do you intend this to be paired with any other libraries to help with this?
Do you have any plans to make dealing with Configuration objects more streamlined? I could imagine a large portion of component code would be dedicated to listening to Changed
events & converting ValueInstance objects into built-in Lua types (e.g., representing an array using a Folder of StringValue objects).
I like the idea of using Configuration for this. Being able to use the WYSIWIG editor is definitely a plus; maybe the next step would be to get the code to run in studio so you could truly understand what you’re getting.
I also think that to convince people to use your library, you’d need to supply a good quantity of common UI components. Buttons, sliders, drop-downs, tabbed views, etc. This will also help give people examples of how to use the library.
Thanks!
I haven’t experimented with this much yet - thankfully there’s plenty of options available since components are just standard OOP objects. One option might be to find the first ancestor with a ‘theme’ component, then you could use that component to store theme information or theme changed events. Alternatively, you could require a module from elsewhere which stores theme stuffs. It’s really up to you!
I plan to use Components to create my plugin UIs in future, so I’ll be tackling this head on. I’ll make sure to share the method which I find works best.
I’ve thought about it a lot, but haven’t settled on a definitive way of doing it yet. Something to keep in mind is that you’re not limited to just value instances - you can use anything! You could pass data via BindableEvents or BindableFunctions, or even use a ModuleScript to store a Lua table. Attributes are a potential future solution too. I’m not sure which solution is the best right now, so I’m leaving it as an open question for further exploration similar to theming.
I’ve already been experimenting with this in private - if I get it running well I’ll definitely share it!
I’m going to look into providing a bunch of reference components for sure! It doesn’t have to be limited to UI either, since Components is designed to be more general purpose. From the sounds of the replies however, that looks like it’ll be a popular use case
I was really thinking about making a component system exactly like the one you made!
But since you already made one. My honor to use it
Keep up the good work. I’m a reallly big fan of your work!
I was also thinking of letting developers to have components to execute inside studio too using plugins.
So you can mimic the way roblox UI components works like a custom UIListLayout or something along the lines!
Is there a way to run a method for every component? For example, if I created a :setText() method in the class and wanted to set the text to something like “Hello, World!” after a button click how would I go about this (similar to state management). Would I need to create a renderstepped event changing the text value from a text value or would there be a simpler way to achieve this?
I was able to do it with a hacky way:
local textBindble = Instance.new("BindableEvent");
return function (Components, class)
function class:construct(instance)
textBindble.Event:Connect(function(text)
instance.Text = text;
end)
instance.MouseButton1Down:Connect(function()
self:setText('Testing2')
end)
end
function class:setText(text)
textBindble:Fire(text)
end
function class:destroy()
self.conn:Disconnect()
end
end
It works okay but I would suggest some sort of addition to the API to run a method on all classes. something like class.run(NAME, …). Not sure on the exact syntax but something built into the API would be amazing.
That’s a good idea - will look into it
I’m looking into including a new function in the next release of Components which should address this use case (and perhaps a few others too!)
Here’s the currently planned API:
function Components.getAllComponentsOfType(className: string) -> array<Component>
It returns an array of component objects, which you could iterate over to call a method on all components. Alternatively, you could change properties en masse, or filter the array to only get specific components.
Here’s a quick example component, called TogglePart, using this new function:
return function(Components, class)
local DEBOUNCE_TIME = 1.5
function class:construct()
self:setToggled(false)
local lastTouchTime = 0
self.touchConn = self.instance.Touched:Connect(function()
local now = os.time()
if now < lastTouchTime + DEBOUNCE_TIME then return end
lastTouchTime = now
for _, component in pairs(Components.getAllComponentsOfType("TogglePart")) do
component:setToggled(not component.isToggled)
end
end)
end
function class:destroy()
self.touchConn:Disconnect()
end
function class:setToggled(isToggled)
self.isToggled = isToggled
self.instance.BrickColor = isToggled and BrickColor.Green() or BrickColor.Red()
end
end
Cool module, I like the object oriented design!
Are there any benefits to this compared to using Tags w/ CollectionService?
If I’m understanding it right, class:construct
seems similar to GetInstanceAddedSignal and class:destroy
seems to be similar to GetInstanceRemovedSignal.
A typo I’m noticing on the wiki page example for GetInstanceAddedSignal is that an onInstanceRemoved function is defined but never called. You’d normally do something like CollectionService:GetInstanceRemovedSignal(InstanceObject):Connect(onInstanceRemovedFunction)
Thanks!
The primary benefits of using Configuration instaces instead of CollectionService are:
- you can store things inside the Configuration instances if you want
- you can easily add and remove Configuration instances in the Explorer, unlike CollectionService tags
- (in the future), you may be able to add attributes to the Configuration instances, keeping them separate from attributes for other components, unlike a CollectionService based system where all attributes would presumably be added to the same instance
However, depending on how things pan out in the future, I’m not against converting to CollectionService if the situation changes.
Yup, that sounds about right.
Might want to post that to #platform-feedback:developer-hub - I don’t have any power over the wiki
That’s amazing! This can unlock so many more possibilities! You’re the best.
edit I changed the snippet to add more tabbing, and the video has been updated.
I wrote a Visual Studio Code snippet that can save you some time when making a new component.
It also moves your cursor to the construct
function, so you can start typing immediately.
"Component": {
"prefix": "component",
"body": [
"return function(Components, class)",
"\t",
"\tfunction class:construct()",
"\t\t$0",
"\tend",
"\t",
"\tfunction class:destroy()",
"\t\t",
"\tend",
"\t",
"end"
],
"description": "Component template"
}
I found out the hard way that apparently class:destroy()
won’t run on client-sided components if the instance is destroyed by the server. The cause is that AncestryChanged
gets disconnected on the client before it can run. Instance.Destroyed
would probably fix this, but it still hasn’t been added =(
Might have to look into an alternate solution.
I have to say I’m quite impressed with this as a framework for fast plugin development. Ideally I’d use Roact but as I was in a hurry, I thought I try out Components, and I had a fully-functioning plugin within about 2 hours (including design/planning).
I’ve actually stalked seen your Twitter and I’m very interested in “Fusion.” It looks like it could be a competitor for Roact but with a much nicer and cleaner API!
That’s awesome! It’s kinda funny that everyone seems to be using Components for UI - I originally designed it to be generic enough for use in all kinds of game systems, like item pickups and buttons and stuff like that.
I’m excited for Fusion too, btw! If or when I release Fusion, it should be a lot better suited to making UI. It’d probably replace this library, since there’s lots of other great techniques for dealing with item pickups and stuff already
I understood the concept, but it really just seemed like a great framework for quick UI development. I personally don’t have any use that I can think of, at this time, for Components outside of UI.
I’m a Roact lover, but it just takes so long to get anything done with it. I can’t wait for Fusion; the syntax and API looks so clean and easy to use, and I’ll definitely be migrating some plugins and UIs over once that’s released. I may even try and integrate my BasicState library too, which currently supports Roact.
Wait a minute, so you are saying that it works exactly like Binders but it’s a standalone module and uses Configuration instead of CollectionService?
In a scenario where I switch from Binder to using this how much effort would it roughly take?
what’s Fusion?!
You literally write a ModuleScript to represent the component, and pop a Configuration, with a matching name, inside any Instance that you want that component applied to (you need to tell Components where to look for your component modules and where to scan for Configurations). It’s that simple!
Fusion is @Elttob’s reactive UI framework that works a bit like Roact (but looks so much nicer). I’m really excited for it after seeing some screenshots and videos on their Twitter.
Do you mean like BindableEvents, or is this something else?
Sorry for the late reply,
That is really cool, thanks for the explanation!