lapis icon indicating copy to clipboard operation
lapis copied to clipboard

Need a way to communicate from view to layout using etlua templating

Open ryanford opened this issue 5 years ago • 4 comments

There are several issues related to this,mainly revolving around content_for and not being able to unescape html sent through from etlua. I would like to request any kind of alternative solution that would allow us to communicate from view back to layout. It doesn't necessarily have to be Moonscript like classes in lua that would allow us to extend the Widget class, or a way to make content_for work. It's completely to your discretion what you can make work, but it's a key feature that your Lua/etlua users can't easily avail at the moment.

I've made a temporary function to let me use the builder syntax in Lua:

-- utils/component.lua
return function(instance, content)
   local _class_0
   local _parent_0 = require("lapis.html").Widget
   local _base_0 = {
      content = content
   }
   _base_0.__index = _base_0
   setmetatable(_base_0, _parent_0.__base)
   _class_0 = setmetatable({
      __init = function(self, ...)
         return _class_0.__parent.__init(self, ...)
      end,
      __base = _base_0,
      __name = nil,
      __parent = _parent_0
   }, {
      __index = function(cls, name)
         local val = rawget(_base_0, name)
         if val == nil then
            local parent = rawget(cls, "__parent")
            if parent then
               return parent[name]
            end
         else
            return val
         end
      end,
      __call = function(cls, ...)
         local _self_0 = setmetatable({}, _base_0)
         cls.__init(_self_0, ...)
         return _self_0
      end
   })
   _base_0.__class = _class_0
   if _parent_0.__inherited then
      _parent_0.__inherited(_parent_0, _class_0)
   end
   return _class_0
end

Basically just abstracted the output from a Moonscript Widget class so I can use it with the builder syntax:

-- test.lua
local component = require("utils.component")
return component("Test", function(self)
   p("Hello")
   div(function()
      return text("Welcome to my site!")
   end)
   return self:content_for("layout-slot", function()
      return p("Greetings from component")
   end)
end)

It works for now, but I greatly prefer the etlua syntax and would love to use it instead. I'm open to whatever kind of clever or disgusting hacks you can come up with. I really have been beating my head on the wall trying to pass an unescaped string through etlua with no success.

Thanks in advanced.

ryanford avatar Aug 03 '18 18:08 ryanford

You're no doubt aware that you can simply set browser-viewable cookies if you need to? I have a couple of React apps I serve with Lapis, so in the relevant handler:

self.cookies.somename = body.access_token

and

app.cookie_attributes = function(self)
  local expires = date(true):adddays(365):fmt("${http}")
  return "Expires=" .. expires .. "; Path=/"
end

timburgess avatar Aug 03 '18 21:08 timburgess

What I'd like to do is basically:

<!-- layout/default.etlua -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <% content_for("css") %>
</head>
<body>
    <main>
        <% content_for("inner") %>
    </main>
    <footer>&copy 2018</footer>
    <% content_for("js") %>
</body>
</html>
-- views/test.lua
local component = require("utils.component") -- the Widget class workaround
return component("Test", function(self)
   self:content_for("css", function()
      return style("body { background-color: #ff9; }") -- send a style sheet to the header
   end)
   p("Hello")
   div(function()
      return text("Welcome to my site!")
   end)
   return self:content_for("js", function() -- send a JS script that runs on load
      return script("alert(1)") 
   end)
end)

This works, but it would be better if I could use etlua for the layout and the component/view. As it is, you can't send raw/unescaped html strings from an etlua view to layout. Is it the end of the world to use the builder syntax? No, but it would be a better developer experience to just use 1 templating system. Honestly, it wouldn't be that bad to use Moonscript in lieu of Widget workaround either, but it's just not ideal.

ryanford avatar Aug 04 '18 01:08 ryanford

What I'd like to do is basically:

<!-- layout/default.etlua -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <% content_for("css") %>
</head>
<body>
    <main>
        <% content_for("inner") %>
    </main>
    <footer>&copy 2018</footer>
    <% content_for("js") %>
</body>
</html>
-- views/test.lua
local component = require("utils.component") -- the Widget class workaround
return component("Test", function(self)
   self:content_for("css", function()
      return style("body { background-color: #ff9; }") -- send a style sheet to the header
   end)
   p("Hello")
   div(function()
      return text("Welcome to my site!")
   end)
   return self:content_for("js", function() -- send a JS script that runs on load
      return script("alert(1)") 
   end)
end)

This works, but it would be better if I could use etlua for the layout and the component/view. As it is, you can't send raw/unescaped html strings from an etlua view to layout. Is it the end of the world to use the builder syntax? No, but it would be a better developer experience to just use 1 templating system. Honestly, it wouldn't be that bad to use Moonscript in lieu of Widget workaround either, but it's just not ideal.

In my case code in test.lua doesn't work like in your example, coz it's generating it as HTML string, so i tried to put it in <% %> brackets, i think it works. Can you show to me how i should require file please? Cannot link it, like you did. I haven't moonscript and i want to make it like in your example. I cannot make require, trying like this - local component = require("../../../lua_modules/share/lua/5.1/lapis/views/layout.lua") Hope on answer

Ennorath avatar Jun 16 '19 12:06 Ennorath

You're no doubt aware that you can simply set browser-viewable cookies if you need to? I have a couple of React apps I serve with Lapis, so in the relevant handler:

self.cookies.somename = body.access_token

and

app.cookie_attributes = function(self)
  local expires = date(true):adddays(365):fmt("${http}")
  return "Expires=" .. expires .. "; Path=/"
end

I'd like to serve React (or more specifically Preact) through a Lapis server - how do you set up Lapis for that? Do you have some kind of an example?

nagdav853 avatar May 11 '20 20:05 nagdav853