Skip to content

Instantly share code, notes, and snippets.

@csqrl
Last active December 5, 2022 18:52
Embed
What would you like to do?

Create.lua

Creates a new Instance with a list of properties, attributes, tags and events. Based on the legacy RbxUtility.Create method.

Creation flow:

  • Create Instance (skipped when using Portals)
  • Apply properties to the Instance
  • Apply CollectionService tags to the Instance
  • Reparent any child Instances to the new Instance
  • Connect events, such as attribute change events, property change events, and Instance events (such as DescendantAdded)
  • Set the Parent of the Instance

Warning: The Parent property is overridden for children. If a child specifies a Parent, it will be ignored, and the child will be parented to the parent in the Create tree.


Table of Contents

  1. API Overview
  2. Introduction
  3. Tags and Attributes
  4. Events and Signals
  5. Children
  6. Constructors
  7. Portals

API Overview

  • Create<T>(className: string)(props: { [any]: any }) -> T - Example
  • Create.Portal<T>(target: T, props: { [any]: any }) -> T - Example
  • Create.Attribute(name: string) -> Symbol - Example
  • Create.Event(name: string) -> Symbol - Example
  • Create.Tag = Symbol - Example
  • Create.Changed(property: string) -> Symbol - Example
  • Create.Changed.Attribute(name: string) -> Symbol - Example

Introduction

Create always returns a physical Instance. Props are static; updating the props will not update the Instance. If this is desired, you may be looking for libraries like Roact or Fusion.

local TextBox = Create "TextBox" {
    ClearTextOnFocus = false,
    BackgroundColor3 = Color3.fromHex("#1a1a1a"),
    TextColor3 = Color3.fromHex("#fafafa"),
    PlaceholderText = "Enter message",
    Parent = PlayerGui.ScreenGui,
    Text = "",

    [Create.Changed "Text"] = function(rbx: TextBox, prev: string)
        print("Text was changed from", prev, "to", rbx.Text)
    end,
}

Tags and Attributes

Attributes and Tags can be applied to an Instance. Tags can either be a string or an array of strings.

local Create = require(path.to.create)
local Attribute, Tags = Create.Attribute, Create.Tag

Create "Configuration" {
    Name = ".config",

    [Attribute "Enabled"] = true,
    [Tags] = { "Configuration", "PluginSettings" },
}

Events and Signals

Create can also connect events to listen for updates on attributes or properties, or connect Instance events.

local Changed, Event = Create.Changed, Create.Event

Create "TextBox" {
    [Attribute "Enabled"] = true,

    [Event "ChildAdded"] = function(rbx: TextBox, child: Instance)
        print("Child", child.Name, "was added to TextBox", rbx.Name)
    end,

    [Changed "Text"] = function(rbx: TextBox, prev: string)
        print("Text was changed from", prev, "to", rbx.Text)
    end,

    [Changed.Attr "Enabled"] = function(rbx: TextBox, prev: boolean?)
        print("TextBox Enabled changed from", prev, "to", rbx:GetAttribute("Enabled"))
        rbx.TextEditable = rbx:GetAttribute("Enabled")
    end,
}

Children

Children can also be specified in the Create props.

Create "Frame" {
    Size = UDim2.fromScale(1, 1),

    Create "TextLabel" {
        Text = "Hello!",
    },

    -- Reparent Baseplate to the new Frame
    workspace.Baseplate,
}

Warning: The Parent property is overridden for children. If a child specifies a Parent, it will be ignored, and the child will be parented to the parent in the Create tree.

Constructors

Each Instance may have a single constructor method. This is called once the Instance has been created and all props have been applied (excluding Parent), and before events are connected.

local reference = nil

Create "Frame" {
    BackgroundColor3 = Color3.fromHex("#00a2ff"),

    [Create] = function(rbx: Frame)
        reference = rbx
    end,
}

Portals

Portals exist to allow you to bind events, properties and children to already existing Instances. This is good when you want to connect events to a service, for example:

local GuiService = game:GetService("GuiService")
local Lighting = game:GetService("Lighting")

local Portal, Event = Create.Portal, Create.Event

local blur = Create "BlurEffect" {
  Size = 0,
  Parent = Lighting,
}

Portal(GuiService) {
  [Event "MenuOpened"] = function()
    blur.Size = 24
  end,
    
  [Event "MenuClosed"] = function()
    blur.Size = 0
  end,
}
--!strict
local CollectionService = game:GetService("CollectionService")
local Creator = {}
local fmt = string.format
local ERROR = {
FUNCTION_ARG = "%s expected %q to be of type %q, but got %q (%s)",
PROPERTY = "Expected %s %q to be of type %q, but got %q (%s)",
VALUE = "Expected %s to be of type %q, but got %q (%s)",
NUMERIC_KEY = 'Numeric keys expect an "Instance", but got %q (%s)',
CONSTRUCTOR = 'A constructor expects a "function", but got %q (%s)',
}
local function SymbolicKey(name: string, valueType: string?)
return function(value: any)
if valueType ~= nil then
assert(
typeof(value) == valueType,
fmt(
ERROR.FUNCTION_ARG,
'SymbolicKey "' .. name .. '"',
"value",
valueType,
tostring(value),
typeof(value)
)
)
end
return {
type = name,
value = value,
}
end
end
local Attribute = SymbolicKey("ATTRIBUTE", "string")
local AttributeChanged = SymbolicKey("ATTRIBUTE_CHANGED", "string")
local Changed = SymbolicKey("CHANGED", "string")
local Event = SymbolicKey("EVENT", "string")
local Tag = SymbolicKey("TAG")(nil)
local Create = nil
local function ApplyProps(target: Instance, props: { [any]: any })
local parent, constructor = nil, nil
local target = target :: any
local events = {
attribute = {},
property = {},
event = {},
}
props = props or {}
for key, value in props do
local keyType, valueType = typeof(key), typeof(value)
if keyType == "string" then
if key == "Parent" then
assert(
valueType == "Instance",
fmt(ERROR.PROPERTY, "property", "Parent", "Instance", tostring(value), valueType)
)
parent = value
else
target[key] = value
end
continue
end
if keyType == "number" then
assert(valueType == "Instance", fmt(ERROR.NUMERIC_KEY, tostring(value), valueType))
value.Parent = target
continue
end
if key == Creator or key == Create then
assert(valueType == "function", fmt(ERROR.CONSTRUCTOR, tostring(value), valueType))
assert(constructor == nil, "Only one constructor may exist per Instance")
constructor = value
continue
end
if keyType == "table" then
local symbolType, symbolValue = key.type, key.value
if symbolType == "ATTRIBUTE" then
target:SetAttribute(symbolValue, value)
elseif symbolType == "ATTRIBUTE_CHANGED" then
events.attribute[symbolValue] = value
elseif symbolType == "CHANGED" then
events.property[symbolValue] = value
elseif symbolType == "EVENT" then
events.event[symbolValue] = value
elseif symbolType == "TAG" then
assert(
valueType == "string" or valueType == "table",
fmt(ERROR.VALUE, "tag", "string | {string}", tostring(value), valueType)
)
if valueType == "string" then
value = { value }
end
for _, tagName in value do
CollectionService:AddTag(target, tagName)
end
end
end
end
if constructor ~= nil then
constructor(target)
end
for watchedAttribute, callback in events.attribute do
local prevValue = target:GetAttribute(watchedAttribute)
target:GetAttributeChangedSignal(watchedAttribute):Connect(function(...)
callback(target, prevValue, ...)
prevValue = target:GetAttribute(watchedAttribute)
end)
end
for watchedProperty, callback in events.property do
local prevValue = target[watchedProperty]
target:GetPropertyChangedSignal(watchedProperty):Connect(function(...)
callback(target, prevValue, ...)
prevValue = target[watchedProperty]
end)
end
for eventName, callback in events.event do
target[eventName]:Connect(function(...)
callback(target, ...)
end)
end
if parent ~= nil then
target.Parent = parent
end
end
local function Create<T>(className: string): (props: { [any]: any }) -> T
assert(
typeof(className) == "string",
fmt(ERROR.FUNCTION_ARG, "Create", "className", "string", tostring(className), typeof(className))
)
return function(props)
local instance = Instance.new(className)
ApplyProps(instance, props)
return instance
end
end
local function Portal<T>(target: T): (props: { [any]: any }) -> T
assert(
typeof(target) == "Instance",
fmt(ERROR.FUNCTION_ARG, "Portal", "target", "Instance", tostring(target), typeof(target))
)
return function(props)
ApplyProps(target, props)
return target
end
end
setmetatable(Creator, {
__call = function(_, ...)
return Create(...)
end,
})
Creator.Create = Create
Creator.Portal = Portal
Creator.Attribute = Attribute
Creator.Attr = Attribute
Creator.Event = Event
Creator.Tag = Tag
Creator.Tags = Tag
Creator.Changed = setmetatable({
Attribute = AttributeChanged,
Attr = AttributeChanged,
}, {
__call = function(_, ...)
return Changed(...)
end,
})
return Creator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment