mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
Implement layouts
Latest Zmpl provides `template.renderWithLayout(other_template, data)`, allowing a template to be renedered within another template. Create layouts in `src/app/views/layouts/` or use `jetzig generate layout [name]` and set `pub const layout = "name";` in each view file.
This commit is contained in:
parent
9be2032e65
commit
aa036fde8b
@ -3,8 +3,8 @@
|
||||
.version = "0.0.0",
|
||||
.dependencies = .{
|
||||
.zmpl = .{
|
||||
.url = "https://github.com/jetzig-framework/zmpl/archive/a39adeb83fdd4363a69754b016988fe092f307e9.tar.gz",
|
||||
.hash = "1220f7fae403e34015012ce486cc3b98c447177353b303978bd267c4cdd938fcc8d5",
|
||||
.url = "https://github.com/jetzig-framework/zmpl/archive/0c889d88a6e84cf821a75219a2473cf3620538a1.tar.gz",
|
||||
.hash = "12200e7ae229283fe737b9d9bee413e3860f76e5d823efd1ee742695463345806bf8",
|
||||
},
|
||||
.args = .{
|
||||
.url = "https://github.com/MasterQ32/zig-args/archive/89f18a104d9c13763b90e97d6b4ce133da8a3e2b.tar.gz",
|
||||
|
@ -2,19 +2,14 @@ const std = @import("std");
|
||||
const args = @import("args");
|
||||
const view = @import("generate/view.zig");
|
||||
const partial = @import("generate/partial.zig");
|
||||
const layout = @import("generate/layout.zig");
|
||||
const middleware = @import("generate/middleware.zig");
|
||||
const util = @import("../util.zig");
|
||||
|
||||
/// Command line options for the `generate` command.
|
||||
pub const Options = struct {
|
||||
path: ?[]const u8 = null,
|
||||
|
||||
pub const shorthands = .{
|
||||
.p = "path",
|
||||
};
|
||||
|
||||
pub const meta = .{
|
||||
.usage_summary = "[view|middleware] [options]",
|
||||
.usage_summary = "[view|partial|layout|middleware] [options]",
|
||||
.full_text =
|
||||
\\Generates scaffolding for views, middleware, and other objects in future.
|
||||
\\
|
||||
@ -33,7 +28,7 @@ pub const Options = struct {
|
||||
};
|
||||
};
|
||||
|
||||
/// Run the `jetzig init` command.
|
||||
/// Run the `jetzig generate` command.
|
||||
pub fn run(
|
||||
allocator: std.mem.Allocator,
|
||||
options: Options,
|
||||
@ -49,7 +44,7 @@ pub fn run(
|
||||
try args.printHelp(Options, "jetzig generate", writer);
|
||||
return;
|
||||
}
|
||||
var generate_type: ?enum { view, partial, middleware } = null;
|
||||
var generate_type: ?enum { view, partial, layout, middleware } = null;
|
||||
var sub_args = std.ArrayList([]const u8).init(allocator);
|
||||
defer sub_args.deinit();
|
||||
|
||||
@ -58,6 +53,8 @@ pub fn run(
|
||||
generate_type = .view;
|
||||
} else if (generate_type == null and std.mem.eql(u8, arg, "partial")) {
|
||||
generate_type = .partial;
|
||||
} else if (generate_type == null and std.mem.eql(u8, arg, "layout")) {
|
||||
generate_type = .layout;
|
||||
} else if (generate_type == null and std.mem.eql(u8, arg, "middleware")) {
|
||||
generate_type = .middleware;
|
||||
} else if (generate_type == null) {
|
||||
@ -72,10 +69,11 @@ pub fn run(
|
||||
return switch (capture) {
|
||||
.view => view.run(allocator, cwd, sub_args.items),
|
||||
.partial => partial.run(allocator, cwd, sub_args.items),
|
||||
.layout => layout.run(allocator, cwd, sub_args.items),
|
||||
.middleware => middleware.run(allocator, cwd, sub_args.items),
|
||||
};
|
||||
} else {
|
||||
std.debug.print("Missing sub-command. Expected: [view|middleware]\n", .{});
|
||||
std.debug.print("Missing sub-command. Expected: [view|partial|layout|middleware]\n", .{});
|
||||
return error.JetzigCommandError;
|
||||
}
|
||||
}
|
||||
|
54
cli/commands/generate/layout.zig
Normal file
54
cli/commands/generate/layout.zig
Normal file
@ -0,0 +1,54 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// Run the layout generator. Create a layout template in `src/app/views/layouts`
|
||||
pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8) !void {
|
||||
if (args.len != 1) {
|
||||
std.debug.print(
|
||||
\\Expected a layout name.
|
||||
\\
|
||||
\\Example:
|
||||
\\
|
||||
\\ jetzig generate layout standard
|
||||
\\
|
||||
, .{});
|
||||
return error.JetzigCommandError;
|
||||
}
|
||||
|
||||
const dir_path = try std.fs.path.join(
|
||||
allocator,
|
||||
&[_][]const u8{ "src", "app", "views", "layouts" },
|
||||
);
|
||||
defer allocator.free(dir_path);
|
||||
|
||||
var dir = try cwd.makeOpenPath(dir_path, .{});
|
||||
defer dir.close();
|
||||
|
||||
const filename = try std.mem.concat(allocator, u8, &[_][]const u8{ args[0], ".zmpl" });
|
||||
defer allocator.free(filename);
|
||||
|
||||
const file = dir.createFile(filename, .{ .exclusive = true }) catch |err| {
|
||||
switch (err) {
|
||||
error.PathAlreadyExists => {
|
||||
std.debug.print("Layout already exists: {s}\n", .{filename});
|
||||
return error.JetzigCommandError;
|
||||
},
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
|
||||
try file.writeAll(
|
||||
\\<html>
|
||||
\\ <head></head>
|
||||
\\ <body>
|
||||
\\ <main>{zmpl.content}</main>
|
||||
\\ </body>
|
||||
\\</html>
|
||||
\\
|
||||
);
|
||||
|
||||
file.close();
|
||||
|
||||
const realpath = try dir.realpathAlloc(allocator, filename);
|
||||
defer allocator.free(realpath);
|
||||
std.debug.print("Generated layout: {s}\n", .{realpath});
|
||||
}
|
17
demo/src/app/views/iguanas.zig
Normal file
17
demo/src/app/views/iguanas.zig
Normal file
@ -0,0 +1,17 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
/// This example uses a layout. A layout is a template that exists in `src/app/views/layouts` and
|
||||
/// references `{zmpl.content}`.
|
||||
///
|
||||
/// The content is the rendered template for the current view which is then injected into the
|
||||
/// layout in place of `{zmpl.content}`.
|
||||
///
|
||||
/// See `demo/src/app/views/layouts/application.zmpl`
|
||||
/// and `demo/src/app/views/iguanas/index.zmpl`
|
||||
pub const layout = "application";
|
||||
|
||||
pub fn index(request: *jetzig.StaticRequest, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
return request.render(.ok);
|
||||
}
|
3
demo/src/app/views/iguanas/index.zmpl
Normal file
3
demo/src/app/views/iguanas/index.zmpl
Normal file
@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<span>Content goes here</span>
|
||||
</div>
|
8
demo/src/app/views/layouts/application.zmpl
Normal file
8
demo/src/app/views/layouts/application.zmpl
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>{zmpl.content}</main>
|
||||
</body>
|
||||
</html>
|
6
demo/src/app/views/layouts/bangbang.zmpl
Normal file
6
demo/src/app/views/layouts/bangbang.zmpl
Normal file
@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<main>{zmpl.content}</main>
|
||||
</body>
|
||||
</html>
|
@ -165,6 +165,7 @@ fn writeRoute(self: *Self, writer: std.ArrayList(u8).Writer, route: Function) !v
|
||||
\\ .action = "{s}",
|
||||
\\ .uri_path = "{s}",
|
||||
\\ .template = "{s}",
|
||||
\\ .module = @import("{s}"),
|
||||
\\ .function = @import("{s}").{s},
|
||||
\\ .params = {s},
|
||||
\\ }},
|
||||
@ -196,6 +197,7 @@ fn writeRoute(self: *Self, writer: std.ArrayList(u8).Writer, route: Function) !v
|
||||
uri_path,
|
||||
full_name,
|
||||
module_path,
|
||||
module_path,
|
||||
route.name,
|
||||
params_buf.items,
|
||||
});
|
||||
|
@ -32,12 +32,18 @@ fn compileStaticRoutes(allocator: std.mem.Allocator) !void {
|
||||
comptime var params: [static_params_len][]const u8 = undefined;
|
||||
inline for (static_route.params, 0..) |json, index| params[index] = json;
|
||||
|
||||
const layout: ?[]const u8 = if (@hasDecl(static_route.module, "layout"))
|
||||
static_route.module.layout
|
||||
else
|
||||
null;
|
||||
|
||||
const route = jetzig.views.Route{
|
||||
.name = static_route.name,
|
||||
.action = @field(jetzig.views.Route.Action, static_route.action),
|
||||
.view = static_view,
|
||||
.static = true,
|
||||
.uri_path = static_route.uri_path,
|
||||
.layout = layout,
|
||||
.template = static_route.template,
|
||||
.json_params = ¶ms,
|
||||
};
|
||||
@ -95,6 +101,24 @@ fn writeContent(
|
||||
std.debug.print("[jetzig] Compiled static route: {s}\n", .{json_path});
|
||||
|
||||
if (zmpl.find(route.template)) |template| {
|
||||
var content: []const u8 = undefined;
|
||||
defer allocator.free(content);
|
||||
|
||||
if (route.layout) |layout_name| {
|
||||
// TODO: Allow user to configure layouts directory other than src/app/views/layouts/
|
||||
const prefixed_name = try std.mem.concat(allocator, u8, &[_][]const u8{ "layouts_", layout_name });
|
||||
defer allocator.free(prefixed_name);
|
||||
|
||||
if (zmpl.find(prefixed_name)) |layout| {
|
||||
content = try template.renderWithLayout(layout, view.data);
|
||||
} else {
|
||||
std.debug.print("Unknown layout: {s}\n", .{layout_name});
|
||||
content = try allocator.dupe(u8, "");
|
||||
}
|
||||
} else {
|
||||
content = try template.render(view.data);
|
||||
}
|
||||
|
||||
const html_path = try std.mem.concat(
|
||||
allocator,
|
||||
u8,
|
||||
@ -102,7 +126,7 @@ fn writeContent(
|
||||
);
|
||||
defer allocator.free(html_path);
|
||||
const html_file = try dir.createFile(html_path, .{ .truncate = true });
|
||||
try html_file.writeAll(try template.render(view.data));
|
||||
try html_file.writeAll(content);
|
||||
defer html_file.close();
|
||||
std.debug.print("[jetzig] Compiled static route: {s}\n", .{html_path});
|
||||
}
|
||||
|
@ -112,12 +112,18 @@ pub fn route(comptime routes: anytype) []views.Route {
|
||||
),
|
||||
};
|
||||
|
||||
const layout: ?[]const u8 = if (@hasDecl(dynamic_route.module, "layout"))
|
||||
dynamic_route.module.layout
|
||||
else
|
||||
null;
|
||||
|
||||
detected[index] = .{
|
||||
.name = dynamic_route.name,
|
||||
.action = @field(views.Route.Action, dynamic_route.action),
|
||||
.view = view,
|
||||
.static = false,
|
||||
.uri_path = dynamic_route.uri_path,
|
||||
.layout = layout,
|
||||
.template = dynamic_route.template,
|
||||
.json_params = &.{},
|
||||
};
|
||||
@ -138,12 +144,18 @@ pub fn route(comptime routes: anytype) []views.Route {
|
||||
comptime var static_params: [params_size][]const u8 = undefined;
|
||||
inline for (static_route.params, 0..) |json, params_index| static_params[params_index] = json;
|
||||
|
||||
const layout: ?[]const u8 = if (@hasDecl(static_route.module, "layout"))
|
||||
static_route.module.layout
|
||||
else
|
||||
null;
|
||||
|
||||
detected[index] = .{
|
||||
.name = static_route.name,
|
||||
.action = @field(views.Route.Action, static_route.action),
|
||||
.view = view,
|
||||
.static = true,
|
||||
.uri_path = static_route.uri_path,
|
||||
.layout = layout,
|
||||
.template = static_route.template,
|
||||
.json_params = &static_params,
|
||||
};
|
||||
|
@ -36,6 +36,7 @@ pub fn start(self: Self, comptime_routes: []jetzig.views.Route) !void {
|
||||
.static = comptime_route.static,
|
||||
.render = comptime_route.render,
|
||||
.renderStatic = comptime_route.renderStatic,
|
||||
.layout = comptime_route.layout,
|
||||
.template = comptime_route.template,
|
||||
.json_params = comptime_route.json_params,
|
||||
};
|
||||
|
@ -210,9 +210,35 @@ fn renderView(
|
||||
if (isBadRequest(err)) return try self.renderBadRequest(request);
|
||||
return try self.renderInternalServerError(request, err);
|
||||
};
|
||||
const content = if (template) |capture| try capture.render(view.data) else "";
|
||||
if (template) |capture| {
|
||||
return .{
|
||||
.view = view,
|
||||
.content = try self.renderTemplateWithLayout(capture, view, route),
|
||||
};
|
||||
} else {
|
||||
// We are rendering JSON, content is ignored.
|
||||
return .{ .view = view, .content = "" };
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .view = view, .content = content };
|
||||
fn renderTemplateWithLayout(
|
||||
self: *Self,
|
||||
template: zmpl.manifest.Template,
|
||||
view: jetzig.views.View,
|
||||
route: *jetzig.views.Route,
|
||||
) ![]const u8 {
|
||||
if (route.layout) |layout_name| {
|
||||
// TODO: Allow user to configure layouts directory other than src/app/views/layouts/
|
||||
const prefixed_name = try std.mem.concat(self.allocator, u8, &[_][]const u8{ "layouts_", layout_name });
|
||||
defer self.allocator.free(prefixed_name);
|
||||
|
||||
if (zmpl.manifest.find(prefixed_name)) |layout| {
|
||||
return try template.renderWithLayout(layout, view.data);
|
||||
} else {
|
||||
self.logger.debug("Unknown layout: {s}", .{layout_name});
|
||||
return try template.render(view.data);
|
||||
}
|
||||
} else return try template.render(view.data);
|
||||
}
|
||||
|
||||
fn isBadRequest(err: anyerror) bool {
|
||||
|
@ -44,6 +44,7 @@ static_view: ?StaticViewType = null,
|
||||
static: bool,
|
||||
render: RenderFn = renderFn,
|
||||
renderStatic: RenderStaticFn = renderStaticFn,
|
||||
layout: ?[]const u8,
|
||||
template: []const u8,
|
||||
json_params: [][]const u8,
|
||||
params: std.ArrayList(*jetzig.data.Data) = undefined,
|
||||
|
Loading…
x
Reference in New Issue
Block a user