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

Get element from array by index #80

Closed
NightFox7 opened this issue Aug 8, 2014 · 7 comments
Closed

Get element from array by index #80

NightFox7 opened this issue Aug 8, 2014 · 7 comments

Comments

@NightFox7
Copy link

Is there a way to get an element from an array using its index?

@groue
Copy link
Owner

groue commented Aug 9, 2014

Hi

No, there is no such feature today. You can use the first and last keys of NSArray, though.

I never had such a need... Would you mind giving an example where such a possibility would be useful?

@groue
Copy link
Owner

groue commented Aug 15, 2014

All right. I'm closing this issue right now.

GRMustache implements the mustache templating language as defined by https://github.com/mustache/spec. And the spec, so far, as no support for accessing array elements by index.

I recommend you open an issue in the very repository of the spec: https://github.com/mustache/spec/issues

@NightFox7
Copy link
Author

Sorry I didn't answer you sooner.
As for the example that you demanded. Well, let's say I have 2 NSArray. Each has some information about a certain object like so:

array1 = [ ['name': 'John', 'lastname': 'doe'], ['firtname': 'John2', 'lastname': 'doe2'] ];
array2 = [ ['age': 20], ['age': 25] ];

I want to display:

John doe is 20 years old.
John2 doe2 is 25 years old.

So my idea was to go through the first array then use the index to get the required object from the second array.

Do you have an alternative way without using the index?

@groue
Copy link
Owner

groue commented Aug 18, 2014

I get it. We could imagine some zip filter, that you could use in a template like:

{{# zip(array1, array2) }}
  {{ name }} {{ lastname }} is {{ age }} years old.
{{/ }}

It's quite possible, using [GRMustacheFilter variadicFilterWithBlock:]. Yet the usual advice in these cases is to give the template data it can render. The template would look like:

{{# zipped_array1_array2) }}
  {{ name }} {{ lastname }} is {{ age }} years old.
{{/ }}

Where zipped_array1_array2 would be pre-computed, or the result of a computed property based on array1 and array2. It is simpler to read and simpler to understand.

Yet a zip filter would be handy in the GRMustache standard library, thanks for the idea :-) You may try to write it if you want :-)

@NightFox7
Copy link
Author

Thanks @groue for your answer.
I am gonna try to implement the zip filter that you suggested and get back to you when it's done.

@groue
Copy link
Owner

groue commented Aug 19, 2014

GRMustache 7.2 has shipped, with built-in zip filter (documentation). Thanks for your contribution 😄

@groue
Copy link
Owner

groue commented Sep 13, 2014

The introduction of the zip filter in the standard library has generated backward compatibility issues (see issue #82).

GRMustache v7.3.0 has shipped without it, in order to restore compatibility.

Below you'll find the documentation for the discontinued zip filter, and its Objective-C code:

zip

Usage:

  • {{# zip(list1, list2, ...) }}...{{/}}

The zip filter iterates several lists all at once. On each step, one object from each input list enters the rendering context, and makes its own keys available for rendering.

Document.mustache:

{{# zip(users, teams, scores) }}
- {{ name }} ({{ team }}): {{ score }} points
{{/}}

data.json:

{
  "users": [
    { "name": "Alice" },
    { "name": "Bob" },
  ],
  "teams": [
    { "team": "iOS" },
    { "team": "Android" },
  ],
  "scores": [
    { "score": 100 },
    { "score": 200 },
  ]
}

Rendering:

- Alice (iOS): 100 points
- Bob (Android): 200 points

In the example above, the first step has consumed (Alice, iOS and 100), and the second one (Bob, Android and 200):

The zip filter renders a section as many times as there are elements in the longest of its argument: exhausted lists simply do not add anything to the rendering context.

id zipFilter = [GRMustacheFilter variadicFilterWithBlock:^id(NSArray *arguments) {

    // GRMustache generally identifies collections as objects conforming
    // to NSFastEnumeration, excluding NSDictionary.
    //
    // Let's validate our arguments first.

    for (id argument in arguments) {
        if (![argument respondsToSelector:@selector(countByEnumeratingWithState:objects:count:)] || [argument isKindOfClass:[NSDictionary class]]) {
            return [GRMustacheRendering renderingObjectWithBlock:^NSString *(GRMustacheTag *tag, GRMustacheContext *context, BOOL *HTMLSafe, NSError **error) {
                if (error) {
                    *error = [NSError errorWithDomain:GRMustacheErrorDomain code:GRMustacheErrorCodeRenderingError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"zip filter in tag %@ requires all its arguments to be enumerable. %@ is not.", tag, argument] }];
                }
                return nil;
            }];
        }
    }


    // Turn NSFastEnumeration arguments into enumerators. This is
    // because enumerators can be iterated all together, when
    // NSFastEnumeration objects can not.

    NSMutableArray *enumerators = [NSMutableArray array];
    for (id argument in arguments) {
        if ([argument respondsToSelector:@selector(objectEnumerator)]) {
            // Assume objectEnumerator method returns what we need.
            [enumerators addObject:[argument objectEnumerator]];
        } else {
            // Turn NSFastEnumeration argument into an array,
            // and extract enumerator from the array.
            NSMutableArray *array = [NSMutableArray array];
            for (id object in argument) {
                [array addObject:object];
            }
            [enumerators addObject:[array objectEnumerator]];
        }
    }


    // Build an array of objects which will perform custom rendering.

    NSMutableArray *renderingObjects = [NSMutableArray array];
    while (YES) {

        // Extract from all iterators the objects that should enter the
        // rendering context at each iteration.
        //
        // Given the [1,2,3], [a,b,c] input collections, those objects
        // would be [1,a] then [2,b] and finally [3,c].

        NSMutableArray *objects = [NSMutableArray array];
        for (NSEnumerator *enumerator in enumerators) {
            id object = [enumerator nextObject];
            if (object) {
                [objects addObject:object];
            }
        }


        // All iterators have been enumerated: stop

        if (objects.count == 0) {
            break;
        }


        // Build a rendering object which extends the rendering context
        // before rendering the tag.

        id<GRMustacheRendering> renderingObject = [GRMustacheRendering renderingObjectWithBlock:^NSString *(GRMustacheTag *tag, GRMustacheContext *context, BOOL *HTMLSafe, NSError **error) {
            for (id object in objects) {
                context = [context contextByAddingObject:object];
            }
            return [tag renderContentWithContext:context HTMLSafe:HTMLSafe error:error];
        }];
        [renderingObjects addObject:renderingObject];
    }

    return renderingObjects;
}];

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

2 participants