Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First impressions #69

Closed
c42f opened this issue Oct 17, 2018 · 13 comments
Closed

First impressions #69

c42f opened this issue Oct 17, 2018 · 13 comments

Comments

@c42f
Copy link

c42f commented Oct 17, 2018

Hey, I've finished going through the README and playing around. I enjoyed the tutorial!

Firstly, I should say I'm impressed by the scope and effort you've already put into this project and its dependencies. There's a lot of work in this and it seems to fit together pretty well.

I did feel that some of the APIs have unexpected surprise choices, from the perspective of someone who's spent a few years using many different packages from the julia ecosystem. For what it's worth here are my first impressions:

Flax

I was surprised by the number of macros in the API and I think it would make sense to replace all these with functions. I can't see why @vars needs to be a macro, nor @foreach or @yield. (@foreach and @yield are also unfortunate choices for names, as they do something different from the Base functions also named foreach and yield). Generally macros should be avoided unless you have a need to actually rewrite syntax, for the simple reason that normal function calls are easier to reason about. For example, @foreach could be rewritten as

function forall(f, arr)
    mapreduce(*, arr, init="") do s
        f(s) * "\n"
    end
end

(I've used a different name here simply because julia Base already has a foreach meaning something subtly different. It's unfortunate because foreach is quite a nice name for what you're trying to do.)

Regarding @vars — would it be possible to use an argument to the generated template to remove the magic from this entirely? For example, digging into the generated flax template for the books example, what if it was generated slightly differently to have a NamedTuple argument:

function func_8f5e1d5a4b008396d4cd553ad4a7c2a06f9eb7c7(vars) 
Flax.skip_element() do;[
	Flax.skip_element() 
	Flax.skip_element() do;[
			Flax.h1() do;[
			"Bill Gates' top $(length(vars.books)) list of recommended books"
		]end

		Flax.ul() do;[
			
        @foreach(vars.books) do book
            "<li>$(book.title) by $(book.author)"
        end
    

		]end

	]end

]end
end

Alternatively, if you don't like the vars.varname syntax, you could use vars[:varname] and pass a normal Dict. If you don't want to use a NamedTuple but do want the dot notation you can do that using a wrapper type for Dict with getproperty implemented.

For the return values of code snippets within templates, you could consider having a customizable mapping — Flax.to_html for example — from the return type of the snippet to a string, rather than simply specifying that all snippets should produce a string directly. For example if my code snippet produced a Book, this could invoke the method

to_html(b::Book) = "$(b.title) by $(b.author)"

More generally, it might be extremely convenient to define

to_html(v::Vector{<:AbstractString}) = join(v, '\n')

so that broadcasting html conversion over collections would "just work" without the need to manually reduce the vector into a string afterward. In fact this could be very convenient and even more efficient with generators, leading to such syntax as

<h1>Bill Gates' top $( length(vars.books) ) recommended books</h1>
<ul>
   <%
      ("<li>$(book.title) by $(book.author)" for book in vars.books)
   %>
</ul>

I'm not sure whether this is a good idea but it might be.

Renderer

I was confused by the ! in the names html! and json!. Normally in julia the ! indicates that the function modifies its argument, but this is clearly not the case here. What am I missing?

Configuration

The data flow around configuration and optional module dependencies is still confusing the heck out of me. See also #67.

SearchLight

Wow, this looks like it was a lot of work! I particularly like the migrations system. I'm terrified by the empty tests directory ;-)

Code Reloading in Dev

I love it! But sometimes it stops working without explanation. I'm unsure whether this is just a limitation/bug in Revise, or something specific to Genie. Unfortunately I haven't been able to catch it in action yet so I can't be more specific.

Logo

The bouncing is highly disturbing each time I see it! Other than that, super cute.

Logging

I think it would be worth considering the new logging frontend macros which I implemented for julia-0.7. I'd love feedback. (I know the available backends in stdlib/Logging are hopelessly limited at this point. That's the next iteration.)

REPL

The genie> prompt is cute, but I think it's more confusing than helpful, given that it is — as far as I can tell — exactly a normal juila prompt with the genie config loaded. I'd prefer a plain julia prompt for this TBH. On the other hand, what might be really cool and useful would be to add a real genie REPL mode where the utility commands from Genie.REPL would be directly accessible.

Default app layout and resource generators

These are enormously helpful, and in conjunction with your tutorial are better than a detailed reference manual (if I had to choose between one or the other). Thanks for providing them.

One thing which is unclear to me is which parts of the default generated application I should add to git before starting real development. In particular the handling of secrets — for example, if config/secrets.jl is secret, presumably it shouldn't go into public source control?

Also, I was confused as to why the utility code inside the generated src directory is not mostly inside Genie itself.

Summary

It's all very interesting and promising. I can see you're working hard to bring some best practices from other web frameworks to Julia. I'm having fun trying to figure out which web framework you're most influenced by (without really knowing or having used any of them). I'll take a guess at phoenix, given that some of your julia code is unusually but pleasantly functional in style in places :-)

@essenciary
Copy link
Member

@c42f Chris, thanks very much for your feedback, much appreciated! I'll try to answer each of them.

Flax

I was surprised by the number of macros in the API and I think it would make sense to replace all these with functions. I can't see why @vars needs to be a macro, nor @foreach or @yield. (@foreach and @yield are also unfortunate choices for names, as they do something different from the Base functions also named foreach and yield). Generally macros should be avoided unless you have a need to actually rewrite syntax, for the simple reason that normal function calls are easier to reason about. (I've used a different name here simply because julia Base already has a foreach meaning something subtly different. It's unfortunate because foreach is quite a nice name for what you're trying to do.)

Honestly, the reason was exactly that -- the names I wanted to use were taken. And to avoid possible name conflicts with user generated variables. And also because visually the @ prefix makes these constructs more visible within the view layer. So yeah, it's abusing the macros syntax, but I believe it's worth doing here.

Regarding @vars — would it be possible to use an argument to the generated template to remove the magic from this entirely?

I kind of like the magic. Magic plays a big part in technology adoption. Here, again, a macro is not necessary, but per above, it indicates that this var is special and it also protects it against being overwritten by a user-defined var.

For the return values of code snippets within templates, you could consider having a customizable mapping — Flax.to_html for example — from the return type of the snippet to a string, rather than simply specifying that all snippets should produce a string directly.

Yes, I thought about this too. Especially as the next step is building a library of more complex UI elements, like charts, dropdowns, etc. Using objects would make these elements mutable, providing some level of flexibility. But a) it would be a huge change (a lot of work); b) I'm concerned about the performance implications of handling large collections of nested objects and keeping these in memory for a long time (until the response is rendered).

So, at least for now, I'll stick to handling these as immutable entities which output strings. However, it's entirely possible to have another templating engine which uses objects. For instance, one of the planned features is to add support for Interact and WebIO.

Renderer

I was confused by the ! in the names html! and json!. Normally in julia the ! indicates that the function modifies its argument, but this is clearly not the case here. What am I missing?

At some level, I ran out of good names. At another level, I think they are actually OK-ish. There are html and json functions (also "public") which return strings. The ! counterparts invoke the corresponding string generating functions and also return the response object to the client. So in a way they are producing a side effect that the developer must be aware of.

Configuration

The data flow around configuration and optional module dependencies is still confusing the heck out of me. See also #67.

Oh yes, that needs some love and good refactoring. Historically it's been a pain as SearchLight was initially part of Genie, and used a single config file. Then they were split but I still wanted to have just one configuration file so it's easier for the users. I've made some progress by extending the config/database.yml file to support SearchLight configuration. So probably that's the way of the future, just separate files for Genie and SearchLight. But this still needs tweaking and the fact that Julia makes external configuration of modules difficult certainly doesn't help.

SearchLight

Wow, this looks like it was a lot of work! I particularly like the migrations system. I'm terrified by the empty tests directory ;-)

Yes, tests everywhere whould be great. Help appreciated. (It's a difficult problem though eh, there's a lot of integration testing needed, against actual DBs, for each supported backend).

Code Reloading in Dev

I love it! But sometimes it stops working without explanation. I'm unsure whether this is just a limitation/bug in Revise, or something specific to Genie. Unfortunately, I haven't been able to catch it in action yet so I can't be more specific.

Not sure what you ran into - from my experience, reloading fails when the changes break the code (so when Revise can't reload the changes because the changes generate errors). Sometimes it's not very obvious that Revise stumbles into an error (not sure why).

Logo

The bouncing is highly disturbing each time I see it! Other than that, super cute.

Hah! Well, the point is not to look at it too much, but to put your own pages there :D

Logging

I think it would be worth considering the new logging frontend macros which I implemented for julia-0.7. I'd love feedback. (I know the available backends in stdlib/Logging are hopelessly limited at this point. That's the next iteration.)

Oh, logging was a nightmare. I'm at the 3rd library and I'm still not happy. The current one introduces a few dependencies which make installation of Genie more brittle. Things can be improved here.

REPL

The genie> prompt is cute, but I think it's more confusing than helpful, given that it is — as far as I can tell — exactly a normal juila prompt with the genie config loaded. I'd prefer a plain julia prompt for this TBH. On the other hand, what might be really cool and useful would be to add a real genie REPL mode where the utility commands from Genie.REPL would be directly accessible.

Yes, a REPL mode would be great, haven't thought about yet. Otherwise I find it useful to see the genie> prompt as an indication that you're within the app's environment.

One thing which is unclear to me is which parts of the default generated application I should add to git before starting real development. In particular the handling of secrets — for example, if config/secrets.jl is secret, presumably it shouldn't go into public source control?

The generated Genie app comes with a .gitignore file which is quite extensive. The config/secrets.jl is added to this .gitignore by default.

Also, I was confused as to why the utility code inside the generated src directory is not mostly inside Genie itself.

It's because a Genie app is a project by itself. And starting with 0.7 libraries (like Genie) can no longer use LOAD_PATH to load modules from external projects. That was a massive rewrite and quite a disappointment for me (discussed at length on discourse). So these had to become parts of the project itself and extra sources of hacky dependency injection, similar to config.

@c42f
Copy link
Author

c42f commented Oct 17, 2018

Honestly, the reason was exactly that -- the names I wanted to use were taken.

Hum, I was afraid you'd say that. The fact that @foreach shares a name with Base.foreach is an excellent reason to avoid it IMO. The julia ecosystem as a whole has strong conventions that a given name should have the same semantics across packages.

And also because visually the @ prefix makes these constructs more visible within the view layer.

A reasonable desire for @vars and @yield which are a little special. Not so much for @foreach, which is an expression which yields a string which is interpolated into the template. Like any other expression the user might dream up.

To explain my source of surprise in these names: as soon as I see them I want to know why they're so special that they can't be done with a normal function.

Regarding @vars — would it be possible to use an argument to the generated template to remove the magic from this entirely?

I kind of like the magic. Magic plays a big part in technology adoption. Here, again, a macro is not necessary, but per above, it indicates that this var is special and it also protects it against being overwritten by a user-defined var.

Fair enough. I do think it's significantly less ergonomic to type @vars(:books) vs vars.books or whatever name you would choose for vars.

For the return values of code snippets within templates, you could consider having a customizable mapping — Flax.to_html for example — from the return type of the snippet to a string, rather than simply specifying that all snippets should produce a string directly.

Yes, I thought about this too. Especially as the next step is building a library of more complex UI elements, like charts, dropdowns, etc. Using objects would make these elements mutable, providing some level of flexibility. But a) it would be a huge change (a lot of work); b) I'm concerned about the performance implications of handling large collections of nested objects and keeping these in memory for a long time (until the response is rendered).

Right. I think this is where allowing generator expressions would really come into their own by creating iterators which could then be traversed, creating the output as you go.

So, at least for now, I'll stick to handling these as immutable entities which output strings.

A good step while the consequences of an alternative API aren't yet clear.

Renderer
I was confused by the ! in the names html! and json!. Normally in julia the ! indicates that the function modifies its argument, but this is clearly not the case here. What am I missing?

At some level, I ran out of good names. At another level, I think they are actually OK-ish. There are html and json functions (also "public") which return strings. The ! counterparts invoke the corresponding string generating functions and also return the response object to the client. So in a way they are producing a side effect that the developer must be aware of.

But they don't have the side effect in and of themselves. It's the fact that their return value is used by the controller which causes the side effect. So... not really a side effect at all?

Configuration
The data flow around configuration and optional module dependencies is still confusing the heck out of me. See also #67.

Oh yes, that needs some love and good refactoring. Historically it's been a pain as SearchLight was initially part of Genie, and used a single config file. Then they were split but I still wanted to have just one configuration file so it's easier for the users. I've made some progress by extending the config/database.yml file to support SearchLight configuration. So probably that's the way of the future, just separate files for Genie and SearchLight. But this still needs tweaking and the fact that Julia makes external configuration of modules difficult certainly doesn't help.

I think that's only true if you think modules should have a global state which is to be configured. If you avoid the globals, and use a context object instead I think all these problems go away. If you really want it to be "more magic", put the context object in the task local storage of your app's task, and discover it from there.

Yes, tests everywhere whould be great. Help appreciated. (It's a difficult problem though eh, there's a lot of integration testing needed, against actual DBs, for each supported backend).

Indeed, though a lot better than your database getting corrupted :-P

The bouncing is highly disturbing each time I see it! Other than that, super cute.

Hah! Well, the point is not to look at it too much, but to put your own pages there :D

Fair point, and I think this would in fact be effective!

Yes, a REPL mode would be great, haven't thought about yet. Otherwise I find it useful to see the genie> prompt as an indication that you're within the app's environment.

Might be good to use the name of the app in that case. Obviously this is all easy to change by just changing the generated genie.jl.

The generated Genie app comes with a .gitignore file which is quite extensive. The config/secrets.jl is added to this .gitignore by default.

Oh I didn't see that (haven't got to adding things to git yet). Good move.

Also, I was confused as to why the utility code inside the generated src directory is not mostly inside Genie itself.

It's because a Genie app is a project by itself. And starting with 0.7 libraries (like Genie) can no longer use LOAD_PATH to load modules from external projects. That was a massive rewrite and quite a disappointment for me (discussed at length on discourse). So these had to become parts of the project itself and extra sources of hacky dependency injection, similar to config.

Well my point here is that much of the utility code looks like it might be rather the same in different genie apps. So if it has a bug, all genie apps generated with the old templates also have that bug, and no amount of upgrading Genie itself will fix them, they'll need to be upgraded one at a time. Granted, I haven't read through that code in detail so it may be more app-specific and customizable than it looks at first glance.

@essenciary
Copy link
Member

Hum, I was afraid you'd say that. The fact that @foreach shares a name with Base.foreach is an excellent reason to avoid it IMO. The julia ecosystem as a whole has strong conventions that a given name should have the same semantics across packages.
And also because visually the @ prefix makes these constructs more visible within the view layer.
A reasonable desire for @vars and @yield which are a little special. Not so much for @foreach, which is an expression which yields a string which is interpolated into the template. Like any other expression the user might dream up.

In this case, I think I did it in order to avoid having to do Flax.foreach(...).

Right. I think this is where allowing generator expressions would really come into their own by creating iterators which could then be traversed, creating the output as you go.

Yes, that would be amazing. I haven't thought so far to be honest.

But they don't have the side effect in and of themselves. It's the fact that their return value is used by the controller which causes the side effect. So... not really a side effect at all?

If you can come up with better names, I'm open to suggestions. Naming things is hard.

I think that's only true if you think modules should have a global state which is to be configured. If you avoid the globals, and use a context object instead I think all these problems go away. If you really want it to be "more magic", put the context object in the task local storage of your app's task, and discover it from there.

Indeed, one of the things I wanted to avoid was to have to pass the DB connection to all the SearchLight methods. task_local_storage might do the trick.

Indeed, though a lot better than your database getting corrupted :-P

Absolutely - PRs are welcome :D

Might be good to use the name of the app in that case. Obviously, this is all easy to change by just changing the generated genie.jl.

Good idea! It would be similar to the pkg mode.

This is actually part of a larger scenario, where I wanted the whole app to be encapsulated in a module named by the actual app. Something for the future.

Well my point here is that much of the utility code looks like it might be rather the same in different genie apps. So if it has a bug, all genie apps generated with the old templates also have that bug, and no amount of upgrading Genie itself will fix them, they'll need to be upgraded one at a time.

That's true, unfortunately. Been there, done that. Again, we're back to my big problem for the last few years: dependency injection in modules and packages. Back when a package could use LOAD_PATH to access user-defined controllers and models the architecture was really clean. Still, I'm sure that the current approach can be improved. For now, I'm happy that it works, as loading and reloading the user-generated code has been a really difficult problem.

My focus is now on making sure that the basic things work well, documentation and tests. With the hope that once these things are available, more people will contribute.

@c42f
Copy link
Author

c42f commented Oct 22, 2018

In this case, I think I did it in order to avoid having to do Flax.foreach(...).

Fair enough, perhaps forall or forevery to at least avoid the pun on Base.foreach which has different semantics? These are still a bit close for comfort though as what you're doing here is really a mapreduce to produce a string. Tricky.

Thinking more generally about this, I think the implementation of forall would best be a lazy map which makes a Generator, and which you can then reduce efficiently into an IOBuffer rather than via string concatenation. Obviously you don't want to use the name lazymap inside the template language because you want a name which is more domain specific, but it's the same thing underneath. Here's a sketch of what I'm thinking:

# In Flax.jl
function forall(f, items)
    (f(i) for i in items)
end

"""
A wrapper for template code fragments, which will efficiently reduce generator expressions
into a string.
"""
function to_html(items)
    io = IOBuffer()
    join(io, items, '\n')
    String(take!(io))
end

to_html(s::AbstractString) = s

Then

<% forall(@vars(:books)) do book %>
    <li>$(book.title) by $(book.author)</li>
<% end %>

should turn into something like

to_html(forall(@vars(:books)) do book
    Flax.li() do
        [
            to_html("$(book.title) by $(book.author)")
        ]
    end
end)

Obviously the more you can hoist the IOBuffer into an outer scope, the fewer allocations you'll get from the generated template code.

If you can come up with better names, I'm open to suggestions. Naming things is hard.

Yep, very hard. I don't think I understand the problem domain here really well, but is the idea that in the context of the Controller, you should be producing various types of HTTP.Response, with the Content-Type set appropriately? In contrast to Flax.html() which is purely in the business of string processing? In that case, I'd suggest just calling it html_response. Then it's clear that we are generating a response rather than a string. On the other hand, this is an improvement on the existing method name respond_with_html, because it doesn't actually do the responding. That's done by whatever picks up the return value from the controller, if I understand correctly.

@aminya
Copy link
Contributor

aminya commented Oct 6, 2019

I have the same problem now. I don't want to use @foreach or @vars inside a separate html file when I can simply pass it to a function
I wrote this controller:

module PeoplesController

using XLSX, DataFrames
people = DataFrame(XLSX.readtable(joinpath("content", "people.xlsx"), "Sheet1")...)

people.Image= "images/people/".*people.Image # adding images folder path


using Genie.Renderer
import Genie.Renderer: tohtml

# a void function that returns the html file.
function peopleRender()
# people is considered as global in this scope for the peopleRender function so it has access to it, and no need to pass it.

    peopleNumber = size(people,1)

    htmlString = ""
    for i=1:peopleNumber
        htmlString = htmlString * peopleView(people[i,:]) # passing one persons data to html code
    end

    teamHTML=tohtml(htmlString, layout = :people, part =:people).body
    return html(teamHTML, layout = :app, part =:people )
end

function peopleView(person)

    if person.Name == "Manager"
        """
        <div class="col-md-20">
            <div class="team">
                <img src=$(person.Image) alt="" class="team-image">
                <h3 class="team-name">$(person.Name)</h3>
                <p>$(person.Description)</p>
                <div class="social-links">
                    <a href="$(person.Email)"><i class="fa fa-mail"></i></a>
                    <a href="$(person.Website)"><i class="fa fa-global"></i></a>
                </div>
            </div>
        </div>
        \n
        """
    else
        """
        <div class="col-md-2">
            <div class="team">
                <img src=$(person.Image) alt="" class="team-image">
                <h3 class="team-name">$(person.Name)</h3>
                <p>$(person.Description)</p>
                <div class="social-links">
                    <a href="$(person.Email)"><i class="fa fa-mail"></i></a>
                    <a href="$(person.Website)"><i class="fa fa-global"></i></a>
                </div>
            </div>
        </div>
        \n
        """
    end

end

end

I wish I could integrate my way of using Genie into the repository. This way is much more natural for making full use of Julia language.

. I still don't know how to have a number of @yield macros in a html layout file and pass different outputs to different parts it! For example, one @yield for filling footer and one for header.

@essenciary
Copy link
Member

@aminya Yes, you can render html from the controller - but I don't fully understand what you're trying to achieve. Do you have an app layout and a peoplelayout? And do you want to render the generated HTML inside the people layout and nest that inside the app layout?

@aminya
Copy link
Contributor

aminya commented Oct 6, 2019

@essenciary Consider my app being the full website layout. I want to make the full layout dynamically adding header footer, mainContent. One of the mainContent for /people/ page will come from people layout.
However, routing is not possible. I don't know how to call multiple renderer functions (header, footer, mainContent) for an address.

This the content I have come up with:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1">

		<title>Test</title>

		<!-- Loading third party fonts -->
		<link href="http://fonts.googleapis.com/css?family=Roboto:300,400,700|" rel="stylesheet" type="text/css">
		<link href="fonts/font-awesome.min.css" rel="stylesheet" type="text/css">

		<!-- Loading main css file -->
		<link rel="stylesheet" href="css/style.css">

		<!--[if lt IE 9]>
		<script src="js/ie-support/html5.js"></script>
		<script src="js/ie-support/respond.js"></script>
		<![endif]-->

	</head>

  <body>
		<div class="site-content">


			<%
				if @vars(:part) == :header
				@yield
				end
			%>

			<main class="main-content">

			<%
				if @vars(:part) == :cards
				@yield
				end
			%>

			<%
				if @vars(:part) == :people
				@yield
				end
			%>
		</main> <!-- .main-content -->

			<!-- footer -->
			<%
				if @vars(:part) == :footer
				@yield
				end
			%>

    <% if Genie.config.websocket_server %>
      <script src="js/app/channels.js"></script>
    <% end %>



	</div>

	<script src="js/jquery-1.11.1.min.js"></script>
	<script src="js/plugins.js"></script>
	<script src="js/app.js"></script>

  </body>


</html>

And here is my people layout:

<div class="fullwidth-block">
	<div class="container">
		<div class="row">
			<div class="col-md-4">
				<h2 class="section-title">Usefull links</h2>
				<p>why?</p>
				<ul class="arrow-list has-border">
					<li>arrow list 1 </li>
					<li>arrow list 2 </li>
				</ul>
			</div>
		</div> <!-- .row -->
	</div> <!-- .container -->
</div> <!-- .fullwidth-block -->

<div class="fullwidth-block" data-bg-color="#edf2f4">
	<div class="container">
		<h2 class="section-title">people</h2>
		<div class="row">
			<%
			if @vars(:part) == :people
			@yield
			end
			%>
		</div> <!-- .row -->
	</div> <!-- .container -->
</div>

So here app layout and its content like header and footer are shared between different pages.
If there is a way to fill in header and footer without any routing that would be awesome! Because that part can be static and generated before site is deployed

@essenciary
Copy link
Member

essenciary commented Oct 6, 2019

@aminya There's a bit of a misunderstanding in the rendering hierarchy. The way Genie rendering is structured is that:

  • there's an application layout - which indeed is meant to render the shell of the web app (the common parts like header, footer, etc)
  • then there's the view - which is meant to output the content coming from the controller, which corresponds to the matched route.

So the output of the Controller is generated with each request and depends on the URL, while the layout is pretty much the same for all the requests.

When you call the html function, it renders the response from the Controller into the view. And then renders it into the layout. The rendering into the layout is done through the @yield macro. Basically, wherever you put @yield into the layout, you'll have whatever you return from the Controller.

The @vars collection holds the values you set from the controller, ie html(:people, :index, some_var = "content to output"). Then you'll be able to access @vars(:some_var) in the views and the layouts. In this example the html function is invoked passing the name of the resource, :people (basically passing the folder app/resources/people) and the name of the view file, index.

You don't necessarily need to use view files - you can output HTML directly from the controller. There's a html method which works with string responses instead of view files: html("content you want to output").

Check out the attached mock app - it will show you the architecture.
LayoutExample.zip

I built this app with only a few commands:

julia> Genie.newapp("LayoutExample")
julia> Genie.newcontroller("People", pluralize = false)
julia> Genie.newcontroller("Cards")

This sets up the app and the Controllers files. Then you add the content into the controllers (seen in the zip). The views need to be added manually. And then you set up the routes. And that's it.

I hope this helps!

PS: Go over the guide if you haven't done so yet: https://genieframework.github.io/Genie.jl/guides/Working_With_Genie_Apps.html

@aminya
Copy link
Contributor

aminya commented Oct 6, 2019

Thank you for your detailed answer! I used the tutorial and your guide, however, here I want to also generate the header and footer using Genie.Flax features.
For example, for header, the name of the website, and the buttons list is read and header HTML is generated.

I don't really care about this being realtime, it can be totally static done once. Because Genie.Flax is very powerful for creating HTML I think it can be used to generate different parts of the shared layout itself! For example, generating different parts of the app.jl.html in an initialization script.

This can be done dynamically too, but the routes.jl probably needs to be changed to execute this initialization script when every link is called

@essenciary
Copy link
Member

@aminya You're welcome

You can do a lot of things in the layout to generate dynamic content. Take a look at the attached updated example - check app/layouts/app.jl.html to see 3 ways of doing that:
1 - render a var generated in the Controller and passed into the view
2 - call a function from the Controller (you'll have to pass the context = @__MODULE__ variable in the html(...) function, so the view gets access to the controller functions, like in the example)
3 - by using view partials to include pieces of layout

PS - I have the feeling that there's probably a simpler to do what you have in mind - the routes changing part doesn't sound like a common workflow.

@essenciary
Copy link
Member

essenciary commented Oct 7, 2019

@aminya BTW, I've seen your comments on Slack - I suggest using Genie's Gitter for support vs the #web Slack channel.

@aminya
Copy link
Contributor

aminya commented Oct 7, 2019

@aminya You're welcome

You can do a lot of things in the layout to generate dynamic content. Take a look at the attached updated example - check app/layouts/app.jl.html to see 3 ways of doing that:
1 - render a var generated in the Controller and passed into the view
2 - call a function from the Controller (you'll have to pass the context = @__MODULE__ variable in the html(...) function, so the view gets access to the controller functions, like in the example)
3 - by using view partials to include pieces of layout

PS - I have the feeling that there's probably a simpler to do what you have in mind - the routes changing part doesn't sound like a common workflow.

Yes! However, I want to fill one single layout/HTML page using different controllers. Consider filling app.jl.html using FootersController.jl, HeadersController.jl, and PeoplesController.jl.
I wrote this macro which does what I want. By passing a symbol to partName from in the controller, we can define the location that @yielding should be done.

using Genie.Flax
macro fill(partName::Symbol)

    :(if @vars(:part) == partName
        @yield
    end)

end

It doesn't work however as expected. I am working on it. We can create a PR and talk about it there if you want. This is a nice feature to add to Genie
Thank you for letting me know about the Gitter!

@essenciary
Copy link
Member

Closing as the API has changed significantly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants