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
Comments
@c42f Chris, thanks very much for your feedback, much appreciated! I'll try to answer each of them.
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
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
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
At some level, I ran out of good names. At another level, I think they are actually OK-ish. There are
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
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).
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).
Hah! Well, the point is not to look at it too much, but to put your own pages there :D
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.
Yes, a REPL mode would be great, haven't thought about yet. Otherwise I find it useful to see the
The generated Genie app comes with a
It's because a Genie app is a project by itself. And starting with 0.7 libraries (like Genie) can no longer use |
Hum, I was afraid you'd say that. The fact that
A reasonable desire for 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.
Fair enough. I do think it's significantly less ergonomic to type
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.
A good step while the consequences of an alternative API aren't yet clear.
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?
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, though a lot better than your database getting corrupted :-P
Fair point, and I think this would in fact be effective!
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
Oh I didn't see that (haven't got to adding things to git yet). Good move.
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. |
In this case, I think I did it in order to avoid having to do
Yes, that would be amazing. I haven't thought so far to be honest.
If you can come up with better names, I'm open to suggestions. Naming things is hard.
Indeed, one of the things I wanted to avoid was to have to pass the DB connection to all the SearchLight methods.
Absolutely - PRs are welcome :D
Good idea! It would be similar to the 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.
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. |
Fair enough, perhaps Thinking more generally about this, I think the implementation of
Then
should turn into something like
Obviously the more you can hoist the
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 |
I have the same problem now. I don't want to use 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 |
@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 |
@essenciary Consider my 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 <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 |
@aminya There's a bit of a misunderstanding in the rendering hierarchy. The way Genie rendering is structured is that:
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 The You don't necessarily need to use view files - you can output HTML directly from the controller. There's a Check out the attached mock app - it will show you the architecture. 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 |
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 I don't really care about this being realtime, it can be totally static done once. Because This can be done dynamically too, but the |
@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 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. |
@aminya BTW, I've seen your comments on Slack - I suggest using Genie's Gitter for support vs the #web Slack channel. |
Yes! However, I want to fill one single layout/HTML page using different controllers. Consider filling 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 |
Closing as the API has changed significantly |
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 namedforeach
andyield
). 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(I've used a different name here simply because julia Base already has a
foreach
meaning something subtly different. It's unfortunate becauseforeach
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 aNamedTuple
argument:Alternatively, if you don't like the
vars.varname
syntax, you could usevars[:varname]
and pass a normalDict
. If you don't want to use aNamedTuple
but do want the dot notation you can do that using a wrapper type forDict
withgetproperty
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 aBook
, this could invoke the methodMore generally, it might be extremely convenient to define
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
I'm not sure whether this is a good idea but it might be.
Renderer
I was confused by the
!
in the nameshtml!
andjson!
. 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 fromGenie.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 insideGenie
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 :-)
The text was updated successfully, but these errors were encountered: