Skip to content
This repository has been archived by the owner on Mar 31, 2023. It is now read-only.

Adds function to replace large class functionality in each component #78

Merged
merged 16 commits into from
Jul 20, 2017

Conversation

evanmwillhite
Copy link
Contributor

@evanmwillhite evanmwillhite commented May 5, 2017

Wouldn't it be lovely if instead of using different syntax for Drupal and Pattern Lab you could simply use this for adding classes <div{{ bem('block') }}> and it just magically worked?

And technically, the syntax is:

<div{{ bem('block', (block_modifiers), block_blockname) }}>

.. with the first being the blockname, the second being modifiers, and the third being a blockname override (modifiers and blockname optional). 💯

Changes:

  • Adds the bem function (components/_twig-components/functions/bem.function.php) to be able to pass in classes to Pattern Lab files (using simply class="") and Drupal files (using {{ attributes }}) correctly.
  • See components/_patterns/01-atoms/02-text/00-headings/heading-1/heading-1.twig and components/_patterns/02-molecules/_block.twig for an example usage of the new bem function. The block twig file is used directly in Drupal and correctly adds the bem class to the existing Drupal attributes

Testing instructions:

  • Install and run vanilla D8 site
  • Enable Emulsify as theme and checkout this branch (performing installation steps of course)
  • Download https://github.com/evanmwillhite/unified-twig-extensions/tree/develop and enable Unified Twig Extensions module.
  • npm start to run Pattern Lab and verify Header 1 is accurately printing classes from file and yml
  • Run Drupal site with preferred method and verify blocks are getting "block" class from components/_patterns/02-molecules/_block.twig as well as Drupal attributes.

To-do:

  • Get forked changes into original Unified Twig Extensions module
  • Add bem function usage in all base files
  • Update docs accordingly with second required Drupal module.

amazingrando
amazingrando previously approved these changes May 8, 2017
Copy link
Member

@amazingrando amazingrando left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@ModulesUnraveled
Copy link
Contributor

@evanmwillhite I'm assuming the answer is yes, but I want to clarify, can we also just do:

class="{{ classes('COMPONENT_NAME', COMPONENT_NAME_blockname) }}"

And, does the order matter? e.g. Could I do this:

class="{{ classes(COMPONENT_NAME_blockname, 'COMPONENT_NAME', COMPONENT_NAME_modifiers) }}

I also just noticed that you have COMPONENT_NAME in single quotes, but not the others. Are the quotes required?

@evanmwillhite
Copy link
Contributor Author

We need to discuss this one before merging because of the ramifications on our Drupal work, but to answer your questions @ModulesUnraveled:

  1. The first arg is currently required (base_class), so you couldn't leave it out although we could change that easily. This would be a string like "link" or "paragraph" - which is why it has quotes around it.

  2. The order does matter, because it is the set order of the args being passed into the function.

@rupl
Copy link

rupl commented May 26, 2017

I would propose you rename this function to bem() or something like that. Classes can and will be added by other sources so a more specific function name would be appreciated at least by me.

/**
* @file
* Add "classes" function for Pattern Lab.
*/
Copy link

@rupl rupl May 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add some params here? would help address @ModulesUnraveled's question regarding 'COMPONENT_NAME' (string) vs COMPONENT_MODIFIERS (array with no quotes)

@evanmwillhite
Copy link
Contributor Author

As discussed in PG, let's see if we can:

  1. only pass in a single class (although I thought we'd like to keep blockname and modifier as optional)
  2. See if we can do this in such a way that Drupal doesn't need the Twig function.

}
if ($blockname) {
$classes[] = $blockname . '__' . $base_class;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the blockname conditional, I wonder if the modifiers loop should be repeated. It's a pattern I rely on frequently enough to standardize. Then you can target a modified component within a parent like so:

// abstract
.blockname__baseclass--modifier {}

// example
.bios__profile--badge-webchef {}

@rupl
Copy link

rupl commented Jun 16, 2017

I have implemented this in a project and the logic works as advertised. 👍

@ccjjmartin
Copy link
Collaborator

This is a great idea and something we need but there is a blocker for this to be compatible with Drupal:
https://www.drupal.org/node/2002606

We could create a sister Emulsify module to provide custom functions and filters until themes can provide their own. But right now this will appear to work within Pattern Lab but then getting it into Drupal will require setting up a custom module (granted it will be small). Example here if this needs to get into an existing client project:

https://api.drupal.org/api/drupal/core%21modules%21system%21tests%21modules%21twig_extension_test%21src%21TwigExtension%21TestExtension.php/class/TestExtension/8.2.x

https://drupal.stackexchange.com/questions/184184/error-when-creating-a-custom-twig-filter

@rupl
Copy link

rupl commented Jun 21, 2017

Yeah there's precedent for having companion modules to themes. The original I know of is Omega Tools and there is also Magic which was intended as more of a shared module for several themes.

The PR linked after my previous comment is not visible to the public, but I started work in another 4K client repo and when it works we can use that as a starter. @ccjjmartin you pasted the 8.2.x link; a word of warning that 8.3.x is significantly different. Plus Drupal 8.4 was released yesterday. Just a heads up if someone decides to dive in.. don't use the old version! I got snagged on that while setting it up.

@rupl
Copy link

rupl commented Jun 21, 2017

Something that had never occurred to me until I was fixing linting errors on this class I'm writing.. shouldn't the args be in a different order? It's called BEM but we're specifying EMB. Changing the order, plus the variable name baseclass to be element would probably smooth a lot of things over for someone who knows BEM but doesn't know Twig, Emulsify, etc.

@evanmwillhite
Copy link
Contributor Author

evanmwillhite commented Jun 21, 2017

OK, I have a lot to say here - sorry I realized as I was writing it out how long-winded it is. :) As far as an add-on module goes, I'm not crazy about the idea as it's yet another thing for Four Kitchens to support and it's currently a trivial amount of functionality to justify it (versus the modules listed above). If we could figure out a way to handle the classes correctly in PL and then deliver them as a string to Drupal attributes that would be ideal (only needing the Twig function on the PL side and therefore no Drupal module), but I can't think of how to do that off the top of my head.

If we do go the module route, we have a couple of options:

  1. Help vet and release https://github.com/drupal-pattern-lab/unified-twig-extensions/tree/develop, which is a module by https://github.com/sghoweri that looks through all the Twig functions, filter and tags in the Pattern Lab directory and makes them available in Drupal. We might need to do some work here for Emulsify to make sure it can search our structure since we don't use the default _source/_twig-components Pattern Lab naming convention but rather components/_twig-components. This has the advantage of support outside the Emulsify community, but has the disadvantage of adding a lot of Twig stuff to Drupal that is unneeded (and/or duplicate).
  2. Create our own module only for this single Twig function. I'll talk more about this in a second.

As for the BEM order, COMPONENT_NAME_blockname is currently just a way of overriding the blockname. So class="{{ classes('COMPONENT_NAME', COMPONENT_NAME_modifiers) }}" for a card component by itself might produce the following valid block and modifier syntax:

<div class="card card--large">

Here, "card" is the block name. When using COMPONENT_NAME_blockname (e.g., to add the following "grid__card" class), it actually currently tacks it on the end like so:

<div class="card card--large grid__card">

This is actually a non-standard BEM usage because there are now 2 blocknames (and 2 styles will be inherited). I think I personally would rather see us either:

  1. get rid of COMPONENT_NAME_blockname as an option and call this what it is which is another modifier (see example below where you still want the "heading-2" styles but want it to look different in the card context):
<h2 class="heading-2 heading-2--card"></h2>

or 2. have the override replace the original like so (and use mixins, etc. to encapsulate any heading styles that you want to "inherit" in multiple places):

<h2 class="card__heading-2"></h2>

In writing this out, I actually think the 2nd option is what we need to do as it is true BEM and is therefore more readable/approachable while also allowing for the flexibility we like in being able to pass in a new block name.

In terms of where we go from here, I see the issue about handling Drupal attributes as being directly related to this PR. I think we should possibly rewriting this Twig function so that the following syntax (or something like it) would create the correct classes in both places:

<div {{ bem('card', card_modifiers, card_blockname) }}>

This would mean adding some functionality to the function to check whether Drupal attributes are in play and create this for PL:

<div class="card card--whatever">

and this for Drupal:

<div{{ attributes.addClass("card card--whatever") }}>

This above to me would justify the module, because it would solve both problems and be a really great addition to the wider Drupal/PL community. What do y'all think?

@ccjjmartin
Copy link
Collaborator

@evanmwillhite I was testing this last night in Drupal and ran into the issue that the Twig Parser class says the function doesn't exist. Example: we name the function bem(). When Drupal does the include on the twig file, bem() will fatal error (WSOD) with a message about bem() is not a declared function. So any non Drupal functions in Pattern Lab will have to be included within Drupal. If there is a module that searches Pattern lab and includes automatically (ideal solution) we should list that module as a dependency to unblock this and other twig functions.

@evanmwillhite
Copy link
Contributor Author

Yea @ccjjmartin that's why this is not merged in yet - because it's currently a blocker for Drupal. If we can't write this as a function that only Pattern Lab would need (which I can't personally think of a way to do), I'd like to see us rewrite this function to print classes for PL and the correct attributes syntax for Drupal (see the last part of my comment above) and release both open-source (neither are Emulsify-specific).

@rupl
Copy link

rupl commented Jun 22, 2017

whew! that is a lot. I have no strong opinions about the BEM really, the element thing was just a random thought I had. I also think a new module to supply the one twig function is overkill, maybe just some documentation or a nice detailed blog post would suffice for helping people implement in Drupal.

@infiniteluke
Copy link
Contributor

That was fun the read, y'all! 💯

@@ -8,6 +8,6 @@
},
"scripts": {
"start": "gulp clean && gulp compile && gulp theme",
"postinstall": "rm -rf pattern-lab && composer create-project -n pattern-lab/edition-twig-standard pattern-lab && rm -rf pattern-lab/source && ln -s ../components pattern-lab/source"
"postinstall": "rm -rf pattern-lab && composer create-project -n pattern-lab/edition-twig-standard pattern-lab && rm -rf pattern-lab/source && ln -s ../components pattern-lab/source && git clone https://github.com/drupal-pattern-lab/bem-twig-extension.git && cp bem-twig-extension/bem.function.php components/_twig-components/functions && rm -rf bem-twig-extension/"
Copy link
Contributor

@infiniteluke infiniteluke Jul 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to consider writing a small node script for this using https://www.npmjs.com/package/fs-extra and https://www.npmjs.com/package/cross-spawn so that emulsify will work on windows machines. If you want help with this, I can carve out some time!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great idea - I created an issue over on emulsify-gulp and assigned you but feel free to move wherever fourkitchens/emulsify-gulp#34

#}
{% set h1_base_class = h1_base_class|default('h1') %}

<h1{{ bem(h1_base_class, (modifiers), blockname) }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, one thing I found out using this on Ithaca, is that if we just use 'modifiers' at all levels, it gets passed down the chain. So, for example, if we set modifiers = [alt, light] to get class="h1-alt h1-light they'll also get passed down to the link component because it also uses the variable modifiers. So we'll end up with something like this:

<h1 class="h1-alt h1-light">
  <a class="link-alt link-light></a>
</h1>

(I've intentionally left out the href etc. to focus on the classes)

I didn't want that in my project, so I used the following in my link and h1 files respectively

<a{{ bem(link_base_class, (link_modifiers), link_blockname) }}>

and

+<h1{{ bem(heading_base_class, (heading_modifiers), heading_blockname) }}>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I wondered if I would have to do this. Done!

@@ -1,6 +1,6 @@
grid_label:
"2-Column Divider Example"
grid_modifiers:
modifiers:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you agree with my last comment about variable inheritance, these should be reverted.

// With Columns
.grid__item {
.grid--columns-2 & {
@if $columns == 2 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! I'll have to try to remember you can do this!


<a class="{{ link_base_class_var }}{% for modifier in link_modifiers %} {{ link_base_class_var }}--{{ modifier }}{% endfor %}{% if link_blockname %} {{ link_blockname }}__{{ link_base_class_var }}{% endif %}" {% for attribute,value in link_attributes %}{{ attribute }}="{{ value }}"{% endfor %} href="{{ link_url }}">
<a{{ bem(link_base_class, (modifiers), blockname) }}{% for attribute,value in link_attributes %} {{ attribute }}="{{ value }}"{% endfor %} href="{{ link_url }}">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these components can span multiple lines because of the way twig handles spaces. It makes it easier to read (classes on one line, attributes on another, href on a third) and tweaks are better represented in diffs since it's not all one line) Here's an example from one of my projects:

{% set link_base_class_var = link_base_class|default('link') %}
<a
  {{ bem(link_base_class_var, (link_modifiers), link_blockname) }}
  {% for attribute,value in link_attributes %}{{ attribute }}="{{ value }}"{% endfor %}
  href="{{ link_url }}"
>
  {% block link_content %}
    {{ link_content }}
  {% endblock %}
</a>

Image is another good use-case:

<img
  {{ bem(link_base_class_var, (link_modifiers), link_blockname) }}
  src="{{ image_src }}"
  {% if image_alt %}
    alt="{{ image_alt }}"
  {% else %}
    aria-hidden="true"
  {% endif %}
>

Link and image are good examples for this since they have multiple distinct parts. If a component only defines classes, I might not do it. e.g. this would look funny and be overkill IMO:

<p
  {{ bem(link_base_class_var, (link_modifiers), link_blockname) }}
>
  {% block paragraph_content %}
    {{ paragraph_content }}
  {% endblock %}
</p>

I'd just put the bem function inline with the

tag and save two lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this and have implemented it where it made sense

@@ -11,9 +11,9 @@
* for example: A formatted text field in Drupal
*/
#}
{% set paragraph_base_class_var = paragraph_base_class|default('paragraph') %}
{% set paragraph_base_class = paragraph_base_class|default('paragraph') %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work? Or does it need to be a separate variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works great!

@@ -12,9 +12,9 @@
* for example: A formatted text field in Drupal
*/
#}
{% set blockquote_base_class_var = blockquote_base_class|default('blockquote') %}
{% set blockquote_base_class = blockquote_base_class|default('blockquote') %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question here

<ol{{ bem(ol_base_class, (modifiers), blockname) }}>
{% for ol_item in ol_items %}
{% include "@atoms/03-lists/_list-item.twig"
with {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we've talked about this, but I think we should standardize on having the with { not break to a new line. So this section would become:

{% include "@atoms/03-lists/_list-item.twig" with {
  "list_item_label": ol_item.label,
  "list_item_content": ol_item.content,
} %}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I like this and I think it's becoming pretty standard. Done!

@@ -1,7 +1,7 @@
button_url:
"#"
"#"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

four spaces

button_content:
"Default Button"
button_url:
"#"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

four spaces

@@ -14,9 +14,9 @@
* for example: to insert an html5 video component
*/
#}
{% set video_base_class_var = video_base_class|default('video') %}
{% set video_base_class = video_base_class|default('video') %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_var?

@@ -0,0 +1,42 @@
<?php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to commit this file? Or will the install scripts thing put it here for us on npm install or yarn install?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't going to commit it, but once it gets added with the install script, it gets added to the repo. And I wasn't sure whether gitignoring it was a good idea either. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we ever update it? And if so, do we want people to be aware of the update?

I'm thinking:
If we won't update it, git ignore it
If we will update it and git ignore it, they won't know there's an update
If we will update it and they commit it, they'll see the update when they do a git diff

Not sure what's right, just laying out the possibilities I see

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, we added it to the install process because we thought we might update it at some point, so... ?

@@ -17,7 +17,7 @@
{% if title %}
{% include "@atoms/02-text/00-headings/heading-1/heading-1.twig"
with {
"heading": title,
"heading": title
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep the , on every line (including only lines, and last lines) so that PRs only show new lines of code and don't report this line as changed when we add a comma because we've added a second line.

Wow... that's kinda confusing... if we don't include the comma here, and then add a line to make it

"heading": title,
"heading_blockname": "awesome",

The diff will show two lines have changed (because of the comma) instead of just showing the one new line as a change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

Copy link
Contributor

@ModulesUnraveled ModulesUnraveled left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned a bunch of stuff, so requesting changes

@ModulesUnraveled ModulesUnraveled dismissed amazingrando’s stale review July 18, 2017 23:04

Thins have changed since this was approved...

@evanmwillhite
Copy link
Contributor Author

@ModulesUnraveled I've made a lot of changes per your review, and if I didn't I tried to address them in comments. Can you take another look when you get a minute? Thank you!

<blockquote{{ bem(blockquote_base_class, (modifiers), blockname) }}>
<blockquote
{{ bem(blockquote_base_class, (blockquote_modifiers), blockquote_blockname) }}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but is a case where I'd probably just keep it inline with blockquote since it's the only attribute we're defining

"heading": "This is an Example Page"
}%}
"heading": "This is an Example Page",
"h1_modifiers": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why I didn't mention this before, but this can be written shorter, like this:

"h1_modifiers": ["margin"],

And I'll give another example of multiples below


{% include "@organisms/card-grid/card-grid.twig" with {
"grid_blockname": "card",
"grid_modifiers": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As promised, here's an example of how to write multiples in the array:

"grid_modifiers": ["divider", "columns-3"],

Fewer lines, easier to read.

Copy link
Contributor

@ModulesUnraveled ModulesUnraveled left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment about where the example page file is placed. With that moved, this is :shipit: as far as I'm concerned.

@@ -0,0 +1,94 @@
<div class="example-page">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that this file is in the 04-templates/example-page directory. I think it should be in the 05-pages directory (and not in a sub-directory) since it contains actual data, and not "replacement blocks".

Copy link
Contributor

@ModulesUnraveled ModulesUnraveled left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@evanmwillhite evanmwillhite merged commit 5143f1d into master Jul 20, 2017
@evanmwillhite evanmwillhite deleted the class-function branch July 20, 2017 02:57
@evanmwillhite evanmwillhite mentioned this pull request Jul 20, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants