Coder Social home page Coder Social logo

Comments (29)

oderwat avatar oderwat commented on May 23, 2024 1

You make all output based on components that render and change the internal state of that component to render its value. Maybe test out the go-nats-app I think there are some things that help to understand the procedure. You could also check out https://github.com/metatexx/go-app-todo which was created by one of our developers to get at speed with go-app.

from go-app.

oderwat avatar oderwat commented on May 23, 2024 1

From that code it is unclear how form will update i.content() but that may be where you use an action handler or (what I would do) add a callback function to form as an external field and call that (if non null) when the content is set.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024 1

Thanks a lot. I'm heading home now and will try to internalize all this. 🤣

from go-app.

oderwat avatar oderwat commented on May 23, 2024 1

Maybe that has to do with Render() is called twice when setting up the component? Once before OnMount() and once after OnMount(). Only OnInit() will be called before the first render and is guaranteed to only run once for a component (even if dismounted and remounted). So what we usually do is letting Render check for c.IsMounted() and return app.Div() in that case.

from go-app.

oderwat avatar oderwat commented on May 23, 2024 1

And also thanks for explaining some of these things. :D Honestly, it was not intuitive to put these things together from the docs. :D

I know, that is why I asked @maxence-charriere to include more real world examples and also documentation of different approaches to set up an app. Our way to handle things is not always the same as @maxence-charriere or @mlctrez (and others) are working in their apps. I try to be quite simple and take the MVC and "callback" approach.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

It looks like the action event relocating to /submit isn't allowing for the OnSubmit to fire.

func (f *form) OnSubmit(ctx app.Context, e app.Event) {
	app.Log("I'm called")
	ctx.SetState("crdData", "Sending this", app.Persist)
}

I'm called isn't there.

from go-app.

oderwat avatar oderwat commented on May 23, 2024

OnSubmit() should not do any submit (prevent default action), because you do not want to actually load/reload another webpage while being in an SPA/PWA. You just use the submit event to gather all the data and send it to the backend in some other way. We use NATS for everything now, but the normal way would be to use HTTP client (or the fetch API of the browser, to save size for the executable) and create a post request.

type exampleData struct {
	First     string
	Second    string
	Checked   bool
	Radio     string
	ComboText string
}

func Render() app.UI {
	return app.Form().ID("Test").Action("#").OnSubmit(c.onSubmit).Body(...)
}

func (c *Form) onSubmit(_ app.Context, e app.Event) {
	//dbg.Logf("%v", ctx.JSSrc().Get("elements").Index(0).Get("name").String())
	e.PreventDefault() // don't reload the page!
	c.apiPutForm()
}

func (c *Form) apiPutForm() {
	dbg.Log()

	var example exampleData

	example.First = c.Text1
	example.Second = c.Text2
	example.Checked = c.Checkbox
	example.Radio = c.Radio
	example.ComboText = c.ComboText

	// initialize http client
	client := &http.Client{}

	// marshal example to json
	jsonData, err := json.Marshal(example)
	if err != nil {
		panic(err)
	}

	// set the HTTP method, url, and request body
	req, err := http.NewRequest(http.MethodPut, "/api/v1/jsondb/example", bytes.NewBuffer(jsonData))
	if err != nil {
		panic(err)
	}

	// set the request header Content-Type for json
	req.Header.Set("Content-Type", "application/json; charset=utf-8")
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}

	// we still read the body of the answer
	buf, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	dbg.Logf("Status: %d\nBody: '%s'", resp.StatusCode, string(buf))
}

Uses dbg() from github.com/metatexx/go-app-pkgs/

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Thanks for the response @oderwat! So, my scenario is that I'm not sending anything to anything. :) Because the parsing is handled in code. I'm just parsing some stuff and displaying it in another thing. So, if I'm in a SPA... That means that I would need to override the innerHTML of something I assume? This is what I'm doing : https://github.com/Skarlso/crd-to-sample-yaml/tree/wasm/wasm

I have two base components... index and crdParser. Index displays the entry point which is a textarea or an input field. Then once submit happens ( or an action, which would be the button's onclick i guess ) I take the data from those fields and pass it to the parser. The parser does one of two things. If the input is from the textarea I parse the content ( in place, no reload, no sending it to the backend.. ) or if it's a URL I fetch the content first and THEN parse it.

In either case, the outcome is then shown by the second component crdParser. Sooooo... I guess I would have to replace the content of some container or something to achieve this?

from go-app.

oderwat avatar oderwat commented on May 23, 2024

Reading your message again, if you want to output "Raw()" HTML you can do that. What strikes me is that a lot of people want to use go-app in a way that it is not meant to be used for. Modifying the DOM should be done with Render() and declarative. Using Raw() or other ways to update the DOM will likely cause problems in the long run.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

No I don't want that. :D I would like to understand how go-app is meant to be used. I find that difficult by just reading the docs to be honest. :D :D

So.. I'm rendering something using a component, and then I update the component's structure and call update.. ? 🤔 Or what's the flow here? :D

I create an onClick for my button, read in the user input, do my parsing, which is done completely using the component! I build up an actual component and call Render which works perfectly BTW. But what I'm struggling with is how I would go from one view to the other basically. So first, get the user input, second display the next component which is the rendered output.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Thanks, I will check out the code for that todo! Hopefully that will lift my fog of understanding a little. :D

from go-app.

oderwat avatar oderwat commented on May 23, 2024

To switch between different views, you could set up a root component that has some "viewID" and make the Render() of this component to render the component that is representing that view (which will be a component too). There is a caveat though. If your different components have the same type (like a "Page" or a "TabContent" component) the way Go-App is updating the DOM may surprise you, as it does not replace a component but updates the first component that was rendered with the component you switched too. Well, to make a long story short: You may need to use something like the "mountpoint" I created which is also in github.com/metatexx/go-app-pkgs/. This then goes in the place where you have the swap able part (based on current viewID) of the root node and makes sure that Go-App sees the switched component as being new and recreate the DOM that represents it.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Hm. What would be the go-app way here that can take user input and then output something based on such input?

I've seen usage of If for example. I could imagine something like and If based on a State for example? I could set the state to the content that user is providing and based on that use a different component?

from go-app.

oderwat avatar oderwat commented on May 23, 2024

I advise against using If() at all, just use a func(condition bool) app.UI{} instead (If() will evaluate all branches always in the current Go-App release).

If you have some input and output, you can create one component which has both as internal state (c.input, c.output), let the Render() decide what to show and have for example a Button() with an OnClick() app.EventHandler which grabs the input in some way. We usually do something like OnInput(c.ValueTo(c.input)) for the text area. Then you calculate the new state, put it into the internal variable. This handler will automatically update by calling Render() and update the DOM with the new outcome. While the OnInput() event will put the data you type into the textarea to the components internal input variable.

Our approach to use Go-App is much like "MVC". Where the "Model" is the components internal fields, the "View" is what render creates, and the "Controller" are all the different Handlers.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Doesn't OnInput fire on each and every character...?

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

This handler will automatically update by calling Render()

The handler can call Render()? 😲

from go-app.

oderwat avatar oderwat commented on May 23, 2024

Doesn't OnInput fire on each and every character...?

Yes, it does. You can add it to the OnClick() if you want. We actually usually have something like a validator or a live update that needs to be called on every input and not only when it gets send. We also most of the time use OnSubmit() and have a "propper" form, that then e.PreventDefault() to mimic what most user expect how a "form" works (like sending it with Return. But that is all situational.

The handler can call Render()? 😲

The handler will schedule an update through the dispatcher and this will call Render() to create the data it needs to compare if an update of the DOM is needed. This all is handled by the UI go routine which actually runs all the time (updating data using a "frame counter"). You never call Render() yourself, and you only need to call c.Update() or use ctx.Dispatch(func(ctx app.Context){}) if you want to make the dispatcher calling an update, when you are outside the normal handlers, like in async code app.Async() / go func(){}()

from go-app.

oderwat avatar oderwat commented on May 23, 2024

You may want to read from: https://go-app.dev/concurrency

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Okay, so I think I'm starting to understand some of the things now. :D

I think something like this might work then:

func (i *index) Render() app.UI {
	return app.Div().Class("container").Body(func() app.UI {
		if i.content != nil {
			return newCrdView(content)
		}

		return app.Div().Class("container").Body(&header{}, &form{})
	}())
}

And content will be populated when I'm calling OnClick once the user finishes entering their YAML value into the textarea. I grab that content, put it into my content thingy and... that's it right? The Render routine will pick up the change and display it. I can add a go back button then which clears the content and the original thing should be displays after a little while...?

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

I was thinking of not using a separate component for form but include the form into index and have index add the callback to onclick.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Nice! So this is working as expected with a hardcoded content for now:

// form is the form in which the user will submit their input.
type form struct {
	app.Compo

	formHandler app.EventHandler
}

func (f *form) Render() app.UI {
	return app.Div().Class("mt-md-20").Body(
		app.Div().Body(
			app.Div().Class("mb-3").Body(
				&textarea{},
				&input{},
			),
			app.Button().Class("btn btn-primary").Type("submit").Style("margin-top", "15px").Text("Submit").OnClick(f.formHandler),
		),
	)
}

func (i *index) OnClick(ctx app.Context, e app.Event) {
	i.content = crdContent
}

func (i *index) Render() app.UI {
	return app.Div().Class("container").Body(func() app.UI {
		if i.content != nil {
			return &crdView{content: i.content}
		}

		return app.Div().Class("container").Body(&header{}, &form{formHandler: i.OnClick})
	}())
}

This will render it through form. Form has an internal handler, which is defined on index. So I still can use the nice component separation.

What is weird, is that when I reload, for some reason, the components are loaded twice? Really odd.

Screenshot 2023-11-30 at 18 22 04

But otherwise it appears that this will work. :D

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Thank you, that's really good to know.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

And also thanks for explaining some of these things. :D Honestly, it was not intuitive to put these things together from the docs. :D

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

I really like this approach. It makes the site easy to follow and render, I think.

Now I just have to get that data from the textarea or the input and I'm golden. :D 🥇

Yah some more real world code and explanation of the structure would be super duper helpful. I really really enjoy working with go-app. :)

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

@oderwat

So what we usually do is letting Render check for c.IsMounted() and return app.Div() in that case.

Errr... how do you do that? Do you implement it with something like this?

func (i *index) OnMount(ctx app.Context) {
	i.isMounted = true
}

func (i *index) Render() app.UI {
	if i.isMounted {
		return app.Div()
	}
	...
}

Or is there something specific for it?

from go-app.

oderwat avatar oderwat commented on May 23, 2024

There is i.Mounted() you do not need to implement it yourself I wrote most of that from memory, so excuse me giving the wrong method name.

from go-app.

oderwat avatar oderwat commented on May 23, 2024

Getting the data can be done in different ways:

  1. You use that c.ValueTo(&c.anything) which is a standard event handler that takes the "value" of the JavaScript element and evaluates it as string and place it in the destination. When you look up the code for it, you find what I usually do where others are using the ID of the JS element
  2. ctx.JSSrc() is the DOM node that was created for the component. So ctx.JSSrc().Get("src") would give you the source of an image in an OnClick() handler. Basically it is as if you set an ID on that element and then use window.GetElementByID('theTID') to get that DOM node.
  3. Using IDs and app.Window().GetElementByID("someID").Get("value").String() also gets you the element with a little roundtrip to JS (and this is why I try to avoid it).
  4. You can also use all the other JS selectors, like so:
    divs := container.Call("getElementsByClassName", "item")
    for i := 0; i < divs.Length(); i++ {
      div := divs.Index(i)
     dbg.Logc(div)
    }
    This is using dbg.Logc() from our packages, which will output the JS object into the dev console so you can inspect it.
  5. You can just store a component or a reference to it inside your component and the just use c.items[i].JSValue() which also references the DOM node that was created for the component.

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Thanks again!

The event based approaches aren't working because the event source isn't the input field or the textarea. For now I went with the ID approach:

func (i *index) OnClick(ctx app.Context, e app.Event) {
	ta := app.Window().GetElementByID("crd_data").Get("value")
	if v := ta.String(); v != "" {
		if len(v) > maximumBytes {
			i.err = errors.New("content exceeds maximum length of 200KB")

			return
		}

		i.content = []byte(v)

		return
	}

	inp := app.Window().GetElementByID("url_to_crd").Get("value")
	if inp.String() == "" {
		return
	}

	f := fetcher.NewFetcher(http.DefaultClient)
	content, err := f.Fetch(inp.String())
	if err != nil {
		i.err = err

		return
	}
	if len(content) > maximumBytes {
		i.err = errors.New("content exceeds maximum length of 200KB")

		return
	}

	i.content = content
}

But number 5 sounds intriguing, actually! I didn't think about that. :D Thanks, I shall try that. :)

from go-app.

Skarlso avatar Skarlso commented on May 23, 2024

Regardless the question is answered I think. :) Thank you very much for ALL the help in this. :)

from go-app.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.