Check if variable is type of string or array in li

2020-08-25 06:05发布

问题:

In Jekyll you can use liquid template and I am trying to write a nav that includes all links in the website.

sitemap:
  home: "/"
  demo:
    right: "/right"
    left: "/left"

What I am trying to achieve is to create a sidebar that icludes all those links. But certains links are under a section. The output should be the following

<nav>
  <ul>
    <li>
      <a href="/">home</a>
    </li>
  </ul>

  <ul>
    <li>demo</li>
    <li>
      <a href="/right">right</a>
    </li>
    <li>
      <a href="/left">left</a>
    </li
  </ul>
</nav>

Not all sections must have a title. The home link is a standalone link. The demo links are all in the demo section.

In liquid I can loop through the sitemap in this way:

{% for nav in site.sitemap %}
<ul>
  <li>{{ nav[0] }}</li>
</ul>
{% endfor %}

In this way, liquid will print home and demo.

What I need is to check if the value is a string or an array in order to print the array or a single link!

Is there a way to check if the liquid variable is a string or an array? I can't find it in the documentation i linked before!

回答1:

Is there a way to check if the liquid variable is a string or an array?


Short answer: Yes

To check if a liquid variable is a string or a list of elements (array or hash), check if it has a first descendant, using the array filter first: {% var.first %} as follow :

{% if var.first %}
  // var is not a string
{% else %}
 // var is a string
{% endif %}


Explanation:

the filter first return the first element of an array or a hash if it has one, if the variable is empty or not a list of element it return nothing, and since nil is a falsy value in liquid, it works the same as false in the if condition above.

e.g :

# var1 = "a"     // string
{% var1.first %} // return:   // nil -> falsy

# var2 = [a,b,c] // array
{% var2.first %} // return: a

# var3 = {k1: a, k2: b, k3: c} // hash
{% var1.first %} // return: k1a

# var4 = {k1, k2, k3: c} // hash, first element is a key without associated value
{% var1.first %} // return: k1


Solution to the OP issue: loop through the sitemap

Now that we can determine if an element is a string or not, we can do that:

{% for nav in site.sitemap %}
<ul>
  <li>
     {{ nav[0] }} :
        {% if nav[1].first %}
        // loop through: nav[1]
        {% else %}
          {{ nav[1] }}
        {% endif %}        
  </li>
</ul>
{% endfor %}

but I found that's more convenient* to work the sitemap as list of elements (a), instead a single big hash (as it is actually) (b) (cf. YAML syntax)

1. Edit the sitemap data structure

so I modified it's structure, by adding a "- " (a dash and a space) before each element:

  - home: "/"
  - demo:
    - right: "/right"
    - left: "/left"  

which give us a:

{"home"=>"/"}{"demo"=>[{"right"=>"/right"}, {"left"=>"/left"}]} 

instead of b:

{"home"=>"/", "demo"=>{"right"=>"/right", "left"=>"/left"}} 

Side Note: you can put the sitemap in it's own file in: _data/sitmap.yml and access it by site.data.sitemap, in lieu of defined it as an attribute in the _config.yml, to keep it cleaner, thereby it'll look exactly as the above.

2. Create a sitemap template

since we'll going to include the sitemap in itself recursively (for each node with children ) we'll put this template in _includes folder and not _layouts

Notice also that Jekyll allows passing parameters to includes. But there is a catch, the param should be a string not an array, or hash. So, we have to make a string out of the links array.

here we go:

<ul>
  <!--                  links is a param -->
  {%for link in include.links %}

    <li>

      {% for part in link %}
        {{part[0]}} : <!-- part[0] : link name  -->

        {% if part[1].first %} <!-- the element has children -->

          <!-- concatenate jekyll array into a string -->
          {% assign _links = "" | split: "" %}
          {% for _link in part[1] %}
            {% assign _links = _links | push: _link %}
          {% endfor %}
          <!-- pass the string as a param to sitemap, then do the recursive dance -->
          {% include sitemap.html links = _links %}

        {% else %} <!-- no children -->

          {{part[1]}} <!-- part[1] : link url -->

        {% endif %}

      {% endfor%}

    </li>

  {%endfor%}

</ul>

3. Use the template :)

<!-- include and init the param with this ↓, or `site.sitemap` if it's defined in `_config.yml`, or ...  -->
{% include sitemap.html links= site.data.sitemap %}

4. Output

for: /_data/sitemap.yml:

- home : "/"
- about: "/about"
- archive:
  - left : "/left"
  - right: "/right"
  - other:
    - up: '/other/up'
    - down: '/other/down'

the template produce:

  • home : /
  • about : /about
  • archive :
    • left : /left
    • right : /right
    • other :
      • up : /other/up
      • down : /other/down

so you have to put part[0] and part[1] of each link in an <a> tag like:

<a href="{{part[1]}}"> {{part[0]}} </a>


回答2:

I'm using Jekyll 2.8.5 and

var[0]

is falsy for string values, but truthy for an array (whose first element is truthy).

(On the other hand var.first, as recommended by the previously accepted answer, now returns the first character of the string, and so it doesn't seem to be a good way to determine if a value is an array anymore.)



回答3:

You can modify your structure as follows:

sitemap:
    home:
        link: "/"
    demo:
        children:
            right:
                link: "/right"
            left:
                link: "/left"

Now all your objects follow the same pattern: instead of testing types, you can just test if an object exists. You can also use recursion by inclusion to parse the sitemap:

{% for nav in site.sitemap %}
<ul>
    {% include 'print-li' %}
</ul>
{% endfor %}

With 'print-li':

<li>
{% if nav.link %}
    <a href="{{ nav.link }}">{{ nav[0] }}</a>
{% else %}
    {{ nav[0] }}
{% endif %}

{% if nav.children %}
    {% for nav in nav.children %}
        {% include 'print-li' %}
    {% endfor %}
{% endif %}
</li>