How to save a TextBox's Text

Yes it is now LOL but now I’m getting this erorr.

image

^ This is line 86.

1 Like

You need to have a “default condition” if the player has no saved data. Something like this:

if NotepadText then
    TextLabel.Text = NotepadText
else
    TextLabel.Text = "Write something here"
end
1 Like

I have a placeholder text that says start taking your first note so I decited to do this instead…

if NotepadText then
    TextLabel.Text = NotepadText
end

So that way the TextBox’s Text is empty.

Anyways, the text I put into the TextBox won’t save, with no errors, maybe you can see what’s up?

LocalScript (a bit of code has been sniped out):

local SaveData = RemoteFunction:InvokeServer(false)
if SaveData then
	TextBox.Text = SaveData
end

ServerScript:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")
local NotepadData = DataStoreService:GetDataStore("NotepadData")
local RemoteFunction = ReplicatedStorage.Events.RemoteFunctions.NotepadData
local LoadedDataBindable = Instance.new("BindableEvent")
local SaveData = {}
local LoadedData = {}

function PlayerAdded(Player)
	local Success, Data = pcall(function() 
		return NotepadData:GetAsync("User_"..Player.UserId)
	end)
	if Success then
		SaveData[Player] = Data
	end
	if not Success then print(Data) end 
	LoadedData[Player] = true
	LoadedDataBindable:Fire()
end

Players.PlayerAdded:Connect(PlayerAdded)

RemoteFunction.OnServerInvoke = function(Player, Save, Data)
	while not LoadedData[Player] do
		LoadedDataBindable.Event:Wait()
	end
end
1 Like

Don’t mean to sound rude, but I was under the impression you were more familiar with Roblox Lua or programming in general. That’s okay, I’m fine holding your hand through this problem — others will be reading this in the future as well, and may have the same kinds of questions.

You left the remainder of this function empty:

RemoteFunction.OnServerInvoke = function(Player, Save, Data)
	while not LoadedData[Player] do
		LoadedDataBindable.Event:Wait()
	end
end

In my first follow-up post, I put this here just so I wasn’t copy-pasting a bunch of code from my initial post:

In actuality, it would look like this:

RemoteFunction.OnServerInvoke = function(Player, Save, Data)
	while not LoadedData[Player] do
		LoadedDataBindable.Event:Wait()
	end
	if Save == true then
        -- If the player called the remote to SAVE their data

        --[[ Make sure you sanitize the data before this point so exploiters
             can't fill up your data stores with junk ]]

        SaveData[Player] = Data
        pcall(function()
            DataStore:SetAsync(Player.UserId, Data)
        end)
    elseif Save == false then
        -- If the player called the remote to RETRIEVE the server's data
        return SaveData[Player]
    end
end

If you used the above code for your Remote.OnServerInvoke function, it would work.

This server code is not finished, however. Note this comment:

It would not be “secure” — a remote exploiter could choose to spam that remote with giant 4 megabyte strings and throttle the server’s data store usage.

Prior to these lines,

make sure you have some cancel conditions if they’re firing the remote too fast or they’re sending through notepad text which is obviously way too long. You’ll need to set some parameters here. Here is how you “cancel” a function:

if 2 + 2 == 5 then return end
print("Test")

Test is not printed because the code was “canceled” with a blank return before that print line could be reached. The code was “canceled” because 2+2 isn’t 5.

If this is all still confusing to you, I can try to elaborate further. I would like to see what methods you try though.

2 Likes

Sorry, I specialize in the UI side of coding on Roblox, not so much game functionality hence why I’m making a notepad.

I did notice though, my LocalScript does not save the data when the player leaves. So I added this.

game.Players.PlayerRemoving:Connect(function(Player)
	RemoteFunction:InvokeServer(true)
end)

Hence this part

if Save == true then
		SaveData[Player] = Data
		pcall(function()
			NotepadData:SetAsync("User_"..Player.UserId, Data)
		end)

is for when the player calls the remote to save their data, though it still won’t work. I’m losing my mind lol

1 Like

I’ve never tested this before. I wonder if it would run if you left the game, since it’s a question of which code gets run first — your PlayerRemoving function or Roblox’s internal script cleanup?

I would not rely on this method. I would just Remote:InvokeServer(true, notepadText) every time I changed the notepad text, and then have the server only run SetAsync when its own PlayerRemoving function is called, using its SaveData table.

In other words, the client cares enough to tell the server every time it makes changes to the notepad text, but the server only cares enough to tell Roblox’s data stores when you leave the game.

Running SetAsync on every remote call is not an efficient use of data stores, even though that’s what I put in the code before. I would recommend to you and any other readers that the server only call Roblox’s data store functions when players join or leave the game, and also perhaps on the server’s own auto saving schedule.

Oh, also you aren’t checking if this Player is the LocalPlayer:

There’s also DataStore2, which I’ve never used or examined in any way, but it’s worth a shot if it helps make this less of a headache.

1 Like

I didn’t know I had to include the TextBox’s text, all I had was Remote:InvokeServer(true).

Oh alright, this should do the trick then.

game.Players.PlayerRemoving:Connect(function(Player)
	local LocalPlayer = game.Players.LocalPlayer
	if Player.Name == LocalPlayer.Name then
		RemoteFunction:InvokeServer(true, TextBox.Text)
	end
end)

Gotcha, I’ll look into it.

1 Like

You have to send the text because the server has to save it. How else would the server know what to save?

1 Like

Lol, you’re right, sorry. I’m getting a new error were the TextBox’s Text gets set to the Data in the localscript

image

1 Like

Print it to debug whats happening

Gotcha. I did a little more work this morning and I found out that my DataStore Key looks like this
image
How would I go about loading and saving the actual string, instead of that whole table, since it looks like that’s what my error was above

Print the Data variable on the server right before you save it so we can see what it is. something tells me it’s not actually the notepad text

In the last few hours I’ve been trying to figure out how to use DataStores and get an actual understanding of them lol, and I’ve finally got a working script. :smiley: I changed your code around a bit, added stuff and removed stuff such as a SavedData table you had, and I added a RemoteEvent to send the Data over.

If you think I should edit some stuff or change some stuff let me know!

Client (some parts have been cut out, but this is the jest):

local NotepadData = ReplicatedStorage.Events.RemoteFunctions.NotepadData
local NotepadSetText = ReplicatedStorage.Events.RemoteEvents.NotepadSetText
local TextBox = script.Parent.TopbarUIs.Notes.Main.Container.TextBox

NotepadSetText.OnClientEvent:Connect(function(Data)
	if Data then
		TextBox.Text = Data
	end
end)

game.Players.PlayerRemoving:Connect(function(Player)
	local LocalPlayer = game.Players.LocalPlayer
	if Player.Name == LocalPlayer.Name then
		NotepadData:InvokeServer(true, TextBox.Text)
		print("(client) "..LocalPlayer.Name.." left, client invoked server to save data: "..TextBox.Text)
	end
end)
-- ^^ not to sure if this works yet. vv this below will change to 60s and not 10s

spawn(function()
	while wait(10) do
		NotepadData:InvokeServer(true, TextBox.Text)
		print("(client) AUTO SAVE, attempted to send over data to server from client: "..TextBox.Text)
	end
end)

Server:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")
local NotepadData = DataStoreService:GetDataStore("NotepadData")
local RemoteFunction = ReplicatedStorage.Events.RemoteFunctions.NotepadData
local RemoteEvent = ReplicatedStorage.Events.RemoteEvents.NotepadSetText
local SavedData = ""

Players.PlayerAdded:Connect(function(Player)
	local Success, Data = pcall(function()
		return NotepadData:GetAsync("User_"..Player.UserId)
	end)
	if Success then
		RemoteEvent:FireClient(Player, Data)
		print("(server) successfully fetched data. data fired to client via remote")
	end
end)

RemoteFunction.OnServerInvoke = function(Player, State, Data)
	if State == true then
		SavedData = Data
		local Success, Data = pcall(function()
			NotepadData:SetAsync("User_"..Player.UserId, Data)
		end)
		if Success then
			print("(server) SUCCESSFULLY SAVED DATA")
		end
	elseif State == false then
		print("(server) is sending over data to client")
		return SavedData
	end
end

I temporally (commented it out) removed this part to see if the game.Players.PlayerRemoving part would work and it didn’t. Do you or anyone else know how I would save the data when the player leaves?

Would you know how I could solve this or if anyone else could?

Don’t use the client PlayerRemoving thing. Do this:

I’ve thought about it but this fires way to many times
image

In the server’s RemoteFunction call, only fill in the SaveData table.

Not sure if you saw my recent script but I ended up removing the SaveData table. I’ve been seeing how I should save the data on the server so I came up with a decent method though it won’t save (text updates and loads the data saved though, the server script just does not save the text)

Client:

NotepadSetText.OnClientEvent:Connect(function(Data)
	if Data then
		TextBox.Text = Data
		if game.Workspace:FindFirstChild(LocalPlayer.Name) then
			game.Workspace[LocalPlayer.Name]:SetAttribute("NotepadData", Data)
		end
	end
end)

TextBox:GetPropertyChangedSignal("Text"):Connect(function()
	if game.Workspace:FindFirstChild(LocalPlayer.Name) then
		game.Workspace[LocalPlayer.Name]:SetAttribute("NotepadData", TextBox.Text)
	end
end)

Server:

game.Players.PlayerRemoving:Connect(function(Player)
	local Character = Player.Character or Player.CharacterAdded:Wait()
	local CharacterNotepadData = Character:GetAttribute("NotepadData")
	pcall(function()
		NotepadData:SetAsync(Player.UserId, CharacterNotepadData)
	end)
end)

If the server’s remote code is only setting an attribute, then the data store throttling from this post shouldn’t be happening.

Also, set the attribute on the player instance, not the character, since you don’t know if the character exists when the PlayerRemoving event fires.

Would you like me to show you an example of a very simple thing I made a while ago which follows the rules I posted about here?