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

Great work #1

Closed
momiji opened this issue Jul 16, 2015 · 15 comments
Closed

Great work #1

momiji opened this issue Jul 16, 2015 · 15 comments

Comments

@momiji
Copy link

momiji commented Jul 16, 2015

Hello, I'm using ninja framwork with rythm engine

I'm a big fan of .net razor syntax, as it is clear and short and very easy to use.
I'm using rythm engine as it is quite the same as razor.

I'm very interested in rocker, except for one thing, I definitely can't miss the hot reloading feature. I understand why you didn't made it, but I'm convinced that this is a must have, especially for our designers.

I'm not an expert in this domain, but I believe that even if the class loader and compile coding is horrible inside rythm code, they have found a good way to do it.

The big plus of your approach, is that everything is compiled into a strongly typed class, which is a very good point.

Maybe a good way to implement hot reloading is to break the strongly typed access to views in the controllers, and fall back to something more classic like.

What I try to mean is that there will be no strong reference to the generated rocker classes from the application, but only in the other way. This allowing to compile generated java classes without recompiling the whole project, which makes UI developping much much faster...

Well I'm sure you understand what I try to (hardly) explain...

Any way, goog work, keep on going !

Note: not everything in rythm is interesting and should be ported, but I think some of them can be usefull, like master pages (I mean the use of @section and @render), and the ability to create functions (see @def, looks like it is the same as calling another template, but with @def one can put all functions in the same file).

If you're interested, I can have a closer look to rythm which I use every day to determine the must have items missing in rocker - I'm sure it's only a very small number of them.

Whatever you decide, bravo

@jjlauer
Copy link
Member

jjlauer commented Jul 16, 2015

I appreciate your feedback. Lots of good points. I agree functions would be great -- as well as assignments.

Master pages -- I personally use the closure syntax -- have you given that a try? You can use any number of other templates and include them.

other.template(param1) -> {

}

Or if you don't need to pass along a block to render you could just do

other.template(param1)

You're correct that the main issue w/ hot reloading is the strongly typed construction between the controller & view code. A potential compromise is 1) adding a weakly-typed alternative or 2) only hot-reloading the logic, but if you change the template interface then you'd need to restart your app. Maybe with signature-checking magic, a friendly error could be rendered that we detected the interface has changed and you need to restart the app.

@momiji
Copy link
Author

momiji commented Jul 19, 2015

Thanks for your comments.

For the second, I was thinkg of option 1, provide a weakly typed method entry to the generated classes, that supports a Map<String,Object> as a unique parameter maybe.

This way, we could completely break the dependency between project and generated classes.
Something like getTemplate(templateName).render(map), as it is done in ninja framework.

For the master pages, this is not quite the same, as master page allows to define more than one section, providing a simple way to build complex pages with a lot of holes in it.

@*
 Example of common header and footer
*@
@args (String title, RockerBody content)

@section("before")
Header with title @title
@content
Footer
@section("after")

and

@*
 Example of index that uses common header and footer
*@
@args (String message)

@views.main.template("Home") -> {

    @render("before") -> {
        This is berfore Header
    }
    Hello @message!
    @render("after") -> {
        This is after Footer
    }
}

@momiji
Copy link
Author

momiji commented Jul 19, 2015

The more I think of it, the more I believe it should be possible to do it, without being to difficult !

index.java

    @Override
    protected void __render() throws IOException, RenderingException {
        // ...
        __internal.renderValue(main.template("Home")
            .__section("before", () -> {})
            .__section("after", () -> {})
            .__body("after", () -> {})
        );
        // ...
    }

main.java

    public main() {
        super();
        __internal.setCharset("UTF-8");
        __internal.setContentType(ContentType.HTML);
        __internal.setTemplateName("main.rocker.html");
        __internal.setTemplatePackageName("views");
        __section("before", () -> {
            __internal.writeValue("This is before Header default content");
        });
    }
    //@Override <= this should exists by default, not something that should be declared in templates
    public main __body(RockerContent body) {
        this.__content = body;
        return this;
    }
    //@Override <= this should exists by default, not something that should be declared in templates
    public main __section(String name, RockerContent body) {
        this.__sections.put(name, body); // will replace default value
        return this;
    }
    @Override
    protected void __render() throws IOException, RenderingException {
        // ...
        // what if section has not been overridden?
        // we should render default value, and if null then nothing - of course !
        __internal.renderValue(this.__sections.get("before"));
        __internal.renderValue(this.__content);
        __internal.renderValue(this.__sections.get("after"));
        );
        // ...
    }

main.rocker.html

@args (String title)
<html>
...
@section("before") { // default content, displayed only if not overridden
    default before
}
...
@section or @ section() ?
...
@section("after") // no default content, so default is empty
...
</html>

index.rocker.html

@main.template("Home") -> {

    @render("before") -> {
        This is berfore Header default content
    }

    Hello @message!

    @render("after") -> {
        This is after Footer
    }
}
...
</html>

Order of @render should not disturb the way it works, I can't remember the rule in .net razor for this, but here we can decide that it does not matter.

@momiji
Copy link
Author

momiji commented Jul 19, 2015

Now with this + another class with hot reloading strategy + a few other minor things to find, and we can have a very very good templating engine :)

@momiji
Copy link
Author

momiji commented Jul 19, 2015

This is my perception of work to do now...

MUST   master pages, of course
MUST   @locale and @i18n
MUST   @def to regroup functions in a single template, maybe generate subclasses ?
MUST   @break and @continue in @for loops
SHOULD @raw() -> {}, and @html @js @json @xml @csv @css => maybe using providers for all these ?
SHOULD @transformer (like extensions in .net - very very powerfull), in that case also allows to "string".raw()
SHOULD @compact and @nocompact
NICE   @cache
NICE   @invoke for dynamic invocation of @def or templates
NICE   @return and @returnIf to immediatly exit rendering
NICE   @verbatim

I see here two point of extensions :

  • for @raw @js ... which in fact defines how content should be rendered
  • for @transformer... which allows to add static utilities and call them with obj.util() instead of StaticClass.util(obj)

See http://rythmengine.org/doc/developer_guide.md#extension for transformers

What do you plan for rocker in the future, is it worth to invest time to work on this ?

@momiji
Copy link
Author

momiji commented Jul 19, 2015

https://github.com/jmarranz/relproxy/ for hot-reloading, looks very nice, isn't it ?

@jjlauer
Copy link
Member

jjlauer commented Sep 18, 2015

@momiji Love your feedback. If you have any interest in implementing some of those features, I'd welcome pull requests. Things like @return and @break and @continue are pretty good, simple ideas. Actually not too bad to implement either. Of course, unit tests for everything is critical.

@jjlauer
Copy link
Member

jjlauer commented Sep 25, 2015

@momiji You'll want to checkout version 0.10.0 which I released a couple hours ago. Hot reloading support in a couple flavors. README is updated with info on it.

Regarding sections, did you take a look at content chunks? I use those to build complex pages. Or I simply write another "method" in its own template file and can call it where needed. Anyway, content chunks looks like this in views/main.rocker.html

@args (String title, RockerContent extracss, RockerContent extrajs, RockerBody content)

<html>
<head>
    <title>@title</title>
    <link rel="stylesheet" href='@N.assetsAt("plugins/bootstrap/css/bootstrap.min.css")' >
    @extracss
    <body>
       @content
    <script type="text/javascript" src='@N.assetsAt("js/jquery-1.10.2.min.js")'></script>
   @extrajs
</body>
</html>

This can be used like so in views/index.rocker.html

@args ()

@extracss -> {
    <link rel="stylesheet" href='@N.assetsAt("css/main.css")' >
}

@views.main.template("Home", extracss, RockerContent.NONE) -> {
<h1>Hello!</h1>
}

This will result in rendering

<html>
<head>
    <title>Home</title>
    <link rel="stylesheet" href='@N.assetsAt("plugins/bootstrap/css/bootstrap.min.css")' >
    <link rel="stylesheet" href='@N.assetsAt("css/main.css")' >
    <body>
       <h1>Hello!</h1>
    <script type="text/javascript" src='@N.assetsAt("js/jquery-1.10.2.min.js")'></script>

</body>
</html>

@rrarrarra
Copy link

Yes, great work!
Maybe i didn't catch it, but is it possible to invoke controller action method directly from within the template and fill the render result content in place:

@controllers.ArticleController.articleShow()

So that the above statement will invoke the controller @controllers.ArticleController's action method articleShow(), and fill the render result content in place of the statement?

I found, that In Play-Framework that is possible in conjunction with rythm-template-engine, as you can read here: https://www.playframework.com/modules/rythm-1.0.0-RC5/user_guide

That would be awesome!

@jjlauer
Copy link
Member

jjlauer commented Dec 6, 2015

Well, you could make it work. The output of the method you are calling
would need to be a String. I'm not sure of the return type of your
controller, but you could write a utility method that converts it to a
string and then call your controller method, and pass its return value to
your utility method to convert it to a String.

For example,
@com.example.Utils.controllerToString(controllers.ArticleController.articleShow())
would then inline the result.

On Sun, Dec 6, 2015 at 1:07 PM, rranke notifications@github.com wrote:

Yes, great work!
Maybe i didn't catch it, but is it possible to invoke controller action
method directly from within the template and fill the render result content
in place:

@controllers.ArticleController.articleShow()

So that the above statement will invoke the controller
@controllers.ArticleController's action method articleShow(), and fill
the render result content in place of the statement?

I found, that In Play-Framework that is possible in conjunction with
rythm-template-engine, as you can read here:
https://www.playframework.com/modules/rythm-1.0.0-RC5/user_guide

That would be awesome!


Reply to this email directly or view it on GitHub
#1 (comment).

@rrarrarra
Copy link

My controller-method is not returning a String, it is returning a ninja.Result.

So, i want to have a controllers template rendered (with all @ -parameters) by the template-engine and would like to "include" the html-output of that rendering-process into another template.

A short and simple example:

My Controller ApplicationController:

public class ApplicationController {
    public Result index() {
        return Results.ok().render(
            views.index.template("IndexTitle")
        );
    }
    public Result show() {
        return Results.ok().render(
            views.show.template("ShowName")
        );
    }
}

Template1 index.rocker.html:

@import controllers.ApplicationController
@args (String title)
<html>
    Hi, i am @title! 
    Lets see how the rendered template of "show()" looks like:
    @controllers.ApplicationController.show()
</html>

Template2 show.rocker.html:

@import controllers.ApplicationController
@args (String name)
<body>
    i am the rendered output with name: @name
</body>

The result of opening http://localhost:8080/index in a webbrowser should be:

<html>
    Hi, i am IndexTitle! 
    Lets see how the rendered template of "show()" looks like:
    <body>
    i am the rendered output with name: ShowName
    </body>
</html>

How to do that?

@jjlauer
Copy link
Member

jjlauer commented Dec 7, 2015

A couple thoughts:

  1. Your example seems like you merely want template reuse, not controller
    reuse. So Template1 could simply be:

@Args (String title)

Hi, i am @title! Lets see how the rendered template of "show()" looks like: @views.show.template("ShowName") 1. Maybe your example is too simplified and you really want controller 2. template reuse. While I mentioned you could write a utility to do that and then convert the ninja.Result to a String -- you'd miss out on Ninja's injection of session variables, parameters, etc. that any useful controller method would use. Seems like poor design to try and reuse a controller method in this way. You should probably refactor your controller methods into more generic, non-ninja, reusable methods that you want to access from other controller methods and/or templates.

-Joe

On Mon, Dec 7, 2015 at 8:25 AM, rranke notifications@github.com wrote:

My controller-method is not returning a String, it is returning a
ninja.Result.

So, i want to have a controllers template rendered (with all @
-parameters) by the template-engine and would like to "include" the
html-output of that rendering-process into another template.

A short and simple example:

My Controller ApplicationController:

public class ApplicationController {
public Result index() {
return Results.ok().render(
views.index.template("IndexTitle")
);
}
public Result show() {
return Results.ok().render(
views.show.template("ShowName")
);
}
}

Template1 index.rocker.html:

@import controllers.ApplicationController
@Args (String title)

Hi, i am @title! Lets see how the rendered template of "show()" looks like: @controllers.ApplicationController.show()

Template2 show.rocker.html:

@import controllers.ApplicationController
@Args (String name)

i am the rendered output with name: @name

The result of opening http://localhost:8080/index in a webbrowser should
be:

Hi, i am IndexTitle! Lets see how the rendered template of "show()" looks like: i am the rendered output with name: ShowName

How to do that?


Reply to this email directly or view it on GitHub
#1 (comment).

@rrarrarra
Copy link

For me, as a backend-developer ("BD"), i have absolutely no idea where exactly the frontend-developer ("FD") wants to place a specific piece of rendered html-code (some java-objects rendered to html-code by the template-engine).

Okay, you think, that my example is too simplified? Yes, you are right :) !

Lets take an example out of the ecommerce-world: A shopping-basket (like an amazon-basket, that lists some products a customer wants to buy).

Lets say, the "FD"-guy wants to place that basket (the content of the basket-Template) in some different places, such as in the footer (the footer-template), the main-page (the main-template) or anywhere else.

For me as a "BD", i have no idea (and i dont care about) where exactly (at which place within the page, the footer or header or...) the basket gets rendered. As a "BD", i only care about that i provide the logic and collect and provide all the data (like user-object, product-objects, basket-object, ...) that is needed ONLY for the purpose to display the shopping-basket.

The "FD"-guy has (and should have) absolutely no idea about which potential data (user-object, product-objects, ...) is needed to calculate and render the shopping-basket-html.
Ideally, the "FD" just calls a simple method "@controllers.ApplicationController.renderShoppingBasket()" to get the rendered html-stuff.
So the interface between the "FD"-guy and the "BD"-guy is this one call to "@controllers.ApplicationController.renderShoppingBasket()", which should return all the rendered html-stuff to display the shopping-basket (and nothing more).
And that call can be flexibly placed ANYWHERE in the homepage, in ANY template, without the requirement of existent variables within the current template.
So that the BD has not to worry about where the FD will finally place the call to the method, and so has not to worry about at the time within the controller, to provide the correct arguments to the template.

The BD has luckily only to calculate and only to provide that stuff, which is directly related to the shopping-basket-data.
So fortunately, the BD therefore has not to calculate and has not to provide any data which is not related to the shopping-cart html-stuff.

This approach should simplify the interface between FD and BD and would increase encapsulation and increases the ability to reuse code.

So that the BD has not to provide the data needed for to render within in controller that is indirectly chosen by the request.
Right now it seems like, that the BD has to calculate, collect and provide ALL the data within the chosen controller (specified in the routes-file), that should get rendered somewhere in the main-template or somewhere in any of the included templates.

What are actually the problems i want so get solved?

The first problem is in the controller:

Right now, unfortunately for me it seems like, that i have to call in EVERY controller (which get called based on the specification of the routes-file) a method, that returns the calculated and collected data to provide ALL the shopping-basket data that should get rendered SOMEWHERE in the main-template or SOMEWHERE in any of the included templates. As a BD, i actually have no idea where exactly this will be printed out. EVERY controller has then to send this calculated data to the template which gets rendered (which brings us to the second problem...)

The second problem is in the templates:

And because this template (which gets rendered after the controller has finished) itself is an include of another template, it has to send the shopping-basket data to its super-template ... and so on). Additionally to that, because the other templates would now need a shopping-basket parameter (argument), we'll have to change every action method in our web-application to pass this parameter. This gets tedious quickly.

What could be the solution?
Right now, i am not quite sure if the above described FD/BD scenario is possible to implement, so that the described problems can be solved and if yes, how to do it.
Or maybe you have just a very short solution to my very long text :)

My Controller ApplicationController:

public class ApplicationController {
    public Result index() {
        return Results.ok().render(
            views.index.template("IndexTitle")
        );
    }

    public String renderShoppingBasket() {
//Get the ninja-Session-object to identify the current user (and get other cookie information)  
//query the database for user-object, shopping-basket-object, product-objects

    //##############################
    // i am not quite sure how to realize the following three steps:
    //##############################

    //1.) set the template that should get rendered
    setTemplateToGetRendered("views.shoppingbasket.rocker.html");

    //2.) pass the following arguments to the template, that are used in rendering process. 
    UnknownType2 renderResult = unknownMethodToRenderTheTemplateByRocker(user-object, shopping-basket-object, products-object);

    //3.) get the rendered HTML-stuff as String
    String renderResultString = unknownMethodToConvertRenderResultIntoString(renderResult);

        return renderResultString;
    }
}

Template1 index.rocker.html:

@import controllers.ApplicationController
@args (String title)
<html>
    Hi, i am @title! 
    the shopping-basket looks like:
    @controllers.ApplicationController.renderShoppingBasket()
</html>

Template2 shoppingbasket.rocker.html:

@import controllers.ApplicationController
@args (User user-object, Basket shopping-basket-object, List<Product> products-object)
<body>
    This is the cart of: @User.name
    These are your products:
    //loop over the @products-object...
</body>

The result of opening http://localhost:8080/index in a webbrowser should be:

<html>
    Hi, i am IndexTitle! 
    the shopping-basket looks like:
    <body>
    This is the cart of: Michael
    These are your products:
    TV, iPhone, iPad
    </body>
</html>

@rrarrarra
Copy link

Haha omg, one line-of-code did the trick.

public String renderShoppingBasket() {
    return views.shoppingbasket.template().render().toString();
}

Found that by just browsing the doku. Please dont say "rtfm" ;)
Thanks anyway for your time and your hints!!

@jjlauer
Copy link
Member

jjlauer commented Dec 11, 2015

@rranke Glad you figured out a solution, although views.shoppingbasket.template().render().toString(); could be inline rendered inside a template directly by doing something like @views.shoppingbasket.template() in another template. However, if you wanted other logic then your solution would work as well. Only other comment is that remember render() will escape characters so if you use that string in another template, you'll likely want to @raw() it -- to prevent duplicate char escaping.

Cheers,
Joe

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

No branches or pull requests

3 participants