stencilproject / Stencil

Stencil is a simple and powerful template language for Swift.
https://stencil.fuller.li
BSD 2-Clause "Simplified" License
2.34k stars 221 forks source link

Include with subcontext not working #223

Open svanimpe opened 5 years ago

svanimpe commented 5 years ago

From https://stencil.fuller.li/en/latest/builtins.html#built-in-tags:

By default the included file gets passed the current context. You can pass a sub context by using an optional 2nd parameter as a lookup in the current context.

{% include "comment.html" comment %}

That's not working for me. I'm getting a "unable to render context" error.

This is my code:

{% include "snippets/date-time-picker.stencil" date %}

date is included in my context (I'm using it in other places as well). Since that snippet only uses date, I was hoping to remove the date. prefix from all my variables.

AliSoftware commented 5 years ago

Is that date key from your parent context containing a string or a full dictionary? If it's not a dictionary that might explain it?

svanimpe commented 5 years ago

It should be a dictionary. It's generated by Kitura from the following Codable struct:

struct DateViewModel: Codable {

    let day: Int
    let month: Int
    let year: Int
    let hour: Int
    let minute: Int

    init(_ components: DateComponents) {
        day = components.day!
        month = components.month!
        year = components.year!
        hour = components.hour!
        minute = components.minute!
    }
}

which is used as a property in my parent context:

let date: DateViewModel
AliSoftware commented 5 years ago

Mmmh strange indeed. Haven't re-tested that recently, but I remember it working (outside of Kitura though, but shouldn't really change much) a while ago…

@ilyapuchka any idea?

kylef commented 5 years ago

The behaviour won't support embedding codable object like you have. The include sub-context must be a dictionary itself.

svanimpe commented 5 years ago

Hmm. I've checked what Kitura does, and it's basically just generating a JSON dictionary from my Codable:

let data = try JSONEncoder().encode(value)
let dict = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]

(somewhat simplified)

In this case, my date variable is probably Any, but it should be castable to [String: Int].

Shouldn't this work?

svanimpe commented 5 years ago

@kylef Do you know if anyone actually uses this feature? I'd love to see some examples of it, so I can figure out what works and what doesn't.

kylef commented 5 years ago

@svanimpe There is a test-case at https://github.com/stencilproject/Stencil/blob/fc404b25d81f93ca48aa48a6fc50a9c0dce88128/Tests/StencilTests/IncludeSpec.swift#L61 perhaps you can adapt that to be closer to what you are trying to achieve to debug.

svanimpe commented 5 years ago

I've tried changing that test to use Codable, as follows:

  $0.it("successfully passes context") {
    struct TestChild: Codable {
        let target: String
    }
    struct TestParent: Codable {
        let child: TestChild
    }
    let parent = TestParent(child: TestChild(target: "World"))
    let data = try JSONEncoder().encode(parent)
    let dict = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
    let template = Template(templateString: "{% include \"test.html\" child %}")
    let context = Context(dictionary: dict, environment: environment)
    let value = try template.render(context)
    try expect(value) == "Hello World!"
  }

But that still passes, so the issue must be elsewhere 🤔