[A/B Testing] Changing parent CSS class based on experiment variable

We have an existing theme which we want to experiment. An example would be to experiment different styles of topic list view. For this we are using Google Optimize A/B testing. Currently we plan to show theme without changes to 50% of the users and rest with updated theme. Original and variant should be defined in the same theme as we are going to experiment with small part. The problem is that the parent CSS classes share the same name and we want to apply styles to those parent classes based on specific variable. How can we apply separate styles to different templates as they share the same parent class name.

Below is a simplified scenario of what we are trying to achieve

Consider I have a theme where topic list style is defined like this in common/common.scss

.topic-list {
  tbody {
    border: none;
  }
  tr {
    border: none;
  }
  .example-div {
    border: none;
  }
}

and in my variant, I want to set it as:

.topic-list {
  tbody {
    display: block;
    tr {
      display: block;
    }
  }
  .example-div {
    border: 2px solid black;
  }
  .another-example-div { 
    font-size: 10px;
  }
}

handlebar files which are being overridden; look like this

javascripts/discourse/templates/list/custom-topic-list-item.hbr

<div class=example-div'>
  Hello
</div>

javascripts/discourse/templates/list/variant-custom-topic-list-item.hbr

<div class=example-div'>
  Hello,
  <div class=another-example-div'>
    Nice to meet you
  </div>
</div>

topic-list class name is mentioned in discourse component but structure is like this after render

<table class="topic-list">
  <tr class="topic-list-item">
   <div class=example-div'>
      Hello
    </div>
  </tr>
</table>

I want to render one of these files depending on user ID in my header file
common/header.html

const { findRawTemplate } = require("discourse-common/lib/raw-templates");
const user_id = api.getCurrentUser().id
 
api.modifyClass('component:topic-list-item', {
  renderTopicListItem() {
    let template = findRawTemplate("list/custom-topic-list-item");
    if (user_id % 2 === 0)
       template = findRawTemplate("list/variant-custom-topic-list-item");
    if (template) {
      this.set("topicListItemContents", template(this).htmlSafe());
    }
  },
});

Thanks

2 Likes

Note that this is probably causing an uncaught error to users who are not logged in. I recommend changing that to

const user = api.getCurrentUser();
if (!user) return;

const userId = user.id;

With that out of the way, let’s get back to your question.

Unlike CSS, SCSS supports a parent selector, so that’s something you can use.

Since you want to apply this to every other user (id % 2)

You can check that when the app loads. So, something like this at the very top

const user = api.getCurrentUser();
const userId = user.id;

if (!userId) return;

const testThemeUser = userId % 2 === 0;

if (testThemeUser) {
  document.body.classList.add("test-theme");
}

You can then use the testThemeUser in your JS conditionals and the test-theme CSS class in your styles.

So, something like this.

JS

let template = findRawTemplate("list/custom-topic-list-item");
if (testThemeUser)
  template = findRawTemplate("list/variant-custom-topic-list-item");
if (template) {
  this.set("topicListItemContents", template(this).htmlSafe());
}

SCSS

.topic-list {
  tbody {
    border: none;
  }
  tr {
    border: none;
  }
  .example-div {
    border: none;
  }
  // test theme SCSS
  .test-theme & {
    .example-div {
      border: 2px solid black;
    }
    .another-example-div {
      font-size: 10px;
    }
  }
}

3 Likes

Thanks for your reply @Johani
Currently I only need to update specific component class that is topic-list, so I am using this approach

const useTestTheme = userId % 2 === 0

api.modifyClass('component:topic-list', {
    classNameBindings: ["test"],
    test: useTestTheme,
})
1 Like