Closed martinjuhasz closed 10 years ago
Hi Martin. So the answer
div should display a text that comes from the current question of the current project. Is it true? If so, if the project global for the whole template rendering, or not?
yes thats what i want to do. the project is global for the template. in fact, questions and project are all subclasses of NSManagedObject.
OK. And the Question class can not have any answer
method, since the only available method is getAnswerForProject:
which requires a project, and has no clue about the global project of the template. Right?
yes. a question can not have a answer property because there are many projects that all have an answer for this question.
in my template i want the answer of a specific project to this question.
if it helps, here is my core data model: https://dl.dropboxusercontent.com/u/80699/model.png
Update your template with <div class="answer">{{ answerTextForProject }}</div>
, and fetch inspiration from the sample code below.
The goal is to have a key which returns an object that:
// {{ answerTextForProject }}
//
// {{# question }}{{ answerTextForProject }}{{/ question }} should render
// [question getAnswerForProject:project].text
//
// So let's have answerTextForProject return an object which simply...
// renders [question getAnswerForProject:project].text :-)
//
// As soon as an object should render in a custom way, GRMustacheRendering
// helps a lot:
id extraKeys = @{ @"answerTextForProject": [GRMustache renderingObjectWithBlock:^NSString *(GRMustacheTag *tag, GRMustacheContext *context, BOOL *HTMLSafe, NSError **error) {
// load the question, and the project from the context stack
MJUProject *project = [context valueForMustacheKey:@"project"];
MJUQuestion *question = [context valueForMustacheKey:@"question"];
// answer
MJUAnswer *answer = [question getAnswerForProject:project];
// render
return answer.text;
}]};
// Insert this new key in the rendering
GRMustacheTemplate *template = nil;
[template renderObjectsFromArray:@[ data, extraKeys ] error:NULL];
Tell me if you have any question.
ok, while trying to implement this, i can't get the current question.
since i loop through multiple questions [context valueForMustacheKey:@"question"];
does not work.
{{#sortedQuestions}}
<div class="question">{{title}}</div>
<div class="answer">{{answerForProject}}</div>
{{/sortedQuestions}}
sortedQuestions
is a method that returns an array of question objects. how do i get the value of the current object of the loop while i'm defining the extraKeys?
How, you are right - sorry, I did not test my code. Try question = [context topMustacheObject]
instead.
This works, thanks!
is this the only way i can "call" methods with parameters?
Well, here is another way to do it, by altering the MJUQuestion class, this time :
// MJUQuestion support for GRMustache
@interface MJUQuestion(GRMustache)
@end
@implementation MJUQuestion(GRMustache)
- (id)answerTextForProject
{
return [GRMustacheFilter filterWithBlock:^id(MJUProject *project) {
MJUAnswer *answer = [self getAnswerForProject:project];
return answer.text;
}];
}
@end
And the template would look like {{#sortedQuestions}}{{ answerTextForProject(project) }}{{/ sortedQuestions}}
.
You may prefer this solution.
is this the only way i can "call" methods with parameters?
Genuine Mustache is all about nested sections which extend a context stack, and extracting values, searching from the top of the context stack.
Your need is quite different: you want to mix several objects taken from different levels in the context stack. This goes well beyond Mustache.
GRMustache won't let you down, but you have to go custom. GRMustacheFilter and GRMustacheRendering are nice and powerful protocols. You may appreciate reading https://github.com/groue/GRMustache/blob/master/Guides/rendering_objects.md and https://github.com/groue/GRMustache/blob/master/Guides/filters.md
Reusing the extraKeys object, you can also write:
// Support for {{ answerText(question, project) }}
id extraKeys = @{ @"answerText": [GRMustacheFilter variadicFilterWithBlock:^id(NSArray *arguments) {
MJUQuestion *question = arguments[0];
MJUQuestion *project = arguments[1];
return [question getAnswerForProject:project].text;
}];
And in the template: {{# questions }} {{ answerText(., project) }}{{/ }}
The choice is yours, depending on how much do you want your custom key to be reusable in other parts of your template, how explicit should be your arguments in the template, how much do you want to pollute your CoreData objects, etc.
I'm sorry there is no unique answer.
Should other attributes of the answer be used, you might also go with:
// Support for `answer(question, project)`
// This filter returns an MJUAsnwer instance.
id extraKeys = @{ @"answer": [GRMustacheFilter variadicFilterWithBlock:^id(NSArray *arguments) {
MJUQuestion *question = arguments[0];
MJUQuestion *project = arguments[1];
return [question getAnswerForProject:project];
}];
And in the template:
{{# questions }}
{{ answer(., project).text }}
{{ dateFormat(answer(., project).date) }}
{{! or... }}
{{# answer(., project) }}
{{ text }}
{{ dateFormat(date) }}
{{/ }}
{{/ }}
Happy Mustache :-)
ok nice. one more thing if you have the time.
i now only want to show this if a project has an answer to a question.
{{#sortedQuestions}}
{{# <!-- [question hasAnswerForProject:project] -->}}
<div class="question">{{title}}</div>
<div class="answer">{{answerForProject}}</div>
{{/ <!-- whatever -->}}
{{/sortedQuestions}}
if i do the same thing here with returning nil if it doesn't exist and a string if it does this (of course) doesn't work.
how would you implement a if statement that needs a object passed to it (project) to determine if it works?
also, something like {{# abc}}
would change the scope for title
and `ànswerForProject`` so that after this addition they wouldn't work anymore, am i right?
what can i do about that?
Doesn't using the answer(question, project)
filter (see https://github.com/groue/GRMustache/issues/73#issuecomment-37429597) solves this?
{{# sortedQuestions }}
{{# answer(., project) }} {{! if there is an answer, put it on the stack }}
<div class="question">{{ title }}</div> {{! loaded from the question, assuming answer has no title }}
<div class="answer">{{ text }}</div> {{! loaded from the answer }}
{{/ }}
{{/ }}
pwew, yeah this works. pretty much stuff to generate this template. i'm not sure that GRMustache is the right approach in my specific case.
anyway, thanks for this great and fast help!
You are welcome, Martin!
GRMustache is a designed to be a Mustache engine which won't let you down.
What does it mean? Mustache (available in Obj-C, Ruby, Javascript, Python, PHP, etc.) is a very minimalistic template engine, which usually force you to prepare your data, which mean preparing a dedicated, maybe complex, object on the side. This object is filled with all the keys expected by the template, and building it eventually turns into a chore, as your templates increase their complexity.
GRMustache adds a layer on top, which allows you to inject directly your raw models, without preparing this dedicated complex object. Increasing the complexity of your template, step after step, is possible without being forced to refactor everything. You can add little touches and helpers, one after the other. This is what I call "not letting your down".
However, eventually, you may want to build a dedicated ViewModel object which encapsulates all your non-trivial needs (see the ViewModel Guide).
Thanks for your nice comments, cheers :-)
yes, i'm probably going to build extra view models for generating my html using GRMustache. For now, this helper functions work perfectly, so thanks again.
About ViewModels : GRMustache7 is about to ship, which removes the support for subclassing GRMustacheContext: you should ignore this part of the guide.
my template looks like the following
to get an answer, i would do something like this in obj-c:
is this possible in any way using grmustache?