# Add background video to certain user profiles?

**URL:** https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916
**Category:** Development
**Created:** [December 2, 2021, 6:57pm UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916 "2021-12-02T18:57:15Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![orangeandblack5](https://avatars.discourse-cdn.com/v4/letter/o/edb3f5/32.png) [@orangeandblack5](https://meta.discourse.org/u/orangeandblack5)
#### Post date: [December 2, 2021, 6:57pm UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916/1 "2021-12-02T18:57:16Z")

</div>

I’m currently attempting to add a video to specific user profile pages, such that all of our patrons would have a certain background video on their profile. (Before you get all up in arms, it’d just be a looping animation, not a full-on video - should look pretty nice, akin to [Steam profile backgrounds](https://steamcommunity.com/id/Doovvaahhkiinn/).)

The following HTML and CSS code works for _all_ users - but obviously that isn’t really what we’re going for:

```plaintext
// This goes in the "Header" tab
<video playsinline autoplay muted loop id="myVideo" poster="[INSERT LINK]">
	<source src="[INSERT LINK]" type="video/webm">
	<source src="[INSERT LINK]" type="video/mp4">
</video>

```

```plaintext
#myVideo {
  position: fixed;
  top: 63px;
  min-height: 1080px;
  margin-left: 50vw;
  transform: translate(-50%);
}

.user-content
{
    background: none;
}

.user-main .about.has-background .details {
    padding-bottom: 15px;
}
.user-main .about
{
    margin-bottom: 0px;
}
.user-content-wrapper
{
    background: rgba(var(--secondary-rgb), 0.8);
}

```

Unlike using `body.category-general` to add an image to just pages in the “general” category, there do not seem to be specific slugs assigned to profile pages of users in a specific group or of a specific username. We’re pretty new to this and mostly have experience with CSS rather than any direct work with the HTML, and so are unsure if there’s a easy/convenient way to make this work as we’d like.

We imagine the best approach would be to add a similar slug for user profiles based on their group, but we are unsure how to make this happen and how to make the video only show on pages with the correct content, and are also not committed to using specifically this approach if another, easier method exists.

For example, we would also be open to the idea of doing this on a per-user basis rather than a per-group basis, if that’s easier somehow.

We’d just prefer not to have to hardcode the video onto _every_ page, so it only loads when you’re on the specific user(s) in question.

**Edit:** I should probably note we’re on the stable branch, in case that changes anything

---

<div class="post-metadata">

### Author: ![orangeandblack5](https://avatars.discourse-cdn.com/v4/letter/o/edb3f5/32.png) [@orangeandblack5](https://meta.discourse.org/u/orangeandblack5)
#### Post date: [December 2, 2021, 10:04pm UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916/2 "2021-12-02T22:04:35Z")

</div>

Our current approach is to see if we can simply detect we’re on a certain user’s page via the canonical link, and if we are, to apply the video. As such, we have the following:

```plaintext
<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() =>{
        determineUser();
    });
    
    function determineUser() {
        var pageURL = document.querySelector("link[rel='canonical']").getAttribute("href");
        var isUserPage = pageURL.includes("https://www.fortressoflies.com/u/");
        document.documentElement.style.setProperty('--currUsername', pageURL);
        if(isUserPage)
        {
            document.documentElement.style.setProperty('--lastUsername', pageURL);
            $('body').css('background-color', '#'+(Math.random()*0xFFFFFF<<0).toString(16));
        }
    }
</script>

```

However, this only seems to work on a full refresh - for whatever reason, clicking around from page to page doesn’t update the --currUsername property, and instead of applying a random color background to user pages, it applies a random color background to all pages if F5 was last hit on a user page, while applying nothing to any page if F5 was last hit on a non-user page.

I’m frankly not experienced enough with JavaScript to know why that would be the case - it seems to me that, on a page change, the function should fire (which it does), causing the pageURL variable to be updated, and this should cause the --currUsername property to be updated when loading a page. However, this only occurs on a full refresh, otherwise the variables don’t seem to change.

Any idea what I’m messing up here?

---

<div class="post-metadata">

### Author: ![orangeandblack5](https://avatars.discourse-cdn.com/v4/letter/o/edb3f5/32.png) [@orangeandblack5](https://meta.discourse.org/u/orangeandblack5)
#### Post date: [December 2, 2021, 10:18pm UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916/3 "2021-12-02T22:18:35Z")

</div>

It seems this is because the canonical URL doesn’t update, while the “og:url” property does.

Only issue is using `var pageURL = document.querySelector("meta[property='og:url']").getAttribute("content");` updates _before_ the meta tag itself is updated - ie, this code gets me the url of the previous page, not the current.

---

<div class="post-metadata">

### Author: ![Johani](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/johani/32/176920_2.png) [@Johani](https://meta.discourse.org/u/Johani)
#### Post date: [December 8, 2021, 7:06pm UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916/4 "2021-12-08T19:06:19Z")

</div>

If you want to add content to a specific page, your best option is a plugin-outlet. In a nutshell, plugin-outlets are spaces reserved in Discourse templates that you can use to add new content.

The first thing you’ll need to find out is if a plugin-outlet exists on the page you want to target. There’s a theme component that you can install that helps you with that.

[(deprecated) Plugin outlet locations theme component](https://meta.discourse.org/t/plugin-outlet-locations-theme-component/100673)

Once you install that component, toggle it, head over to the page you want to target and check what you have to work with. In your case, such a plugin-outlet exists (highlighted in green)

 ![plugin-outlet on user profile page](https://global.discourse-cdn.com/meta/original/3X/0/a/0ab73695a813b551f47e77d5cd8141d19cfe6096.png)

So, the one we want is `above-user-profile`

Suppose that it didn’t exist… what then? The best option, in that case, is to ask for it to be added or send a PR to add it to core. It will get accepted most of the time if your use-case makes sense.

Anyway, as I said, it already exists in this case. So, let’s see how you can add markup to it. You won’t need the component above for the rest of this, so you can disable it now since you already have the name of the plugin outlet.

All you need to do is add something like this in the header tab of your theme.

```xml
<script type="text/x-handlebars" data-template-name="/connectors/OUTLET_NAME/SOME_NAME">
  Your markup goes here...
</script>

```

You need to change `OUTLET_NAME` to the name of the outlet you want to target. Then change `SOME_NAME` to the name you want to give this customization. The name can be anything, but try to be descriptive if you can. It’s a good practice. So, we end up with this.

```xml
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Your markup goes here...like
  <h1>Hello World!!</h1>
</script>

```

Let’s try that and see what happens… remember, the snippet above goes in your theme’s `common > header` tab.

and…

 ![adding data to plugin outlet works](https://global.discourse-cdn.com/meta/original/3X/6/9/691b78b8f0ec7ad337e8ac026a444fd7e64ed99e.png)

So far so good, but let’s dig deeper.

You don’t want your videos to show on every profile, and you want them to only show based on some condition. So, how do you do that? Well, you’ll need two things, some data to consume and a bit of Javascript.

Let’s find the data. Remember when I said plugin-outlets are reserved spaces? Well, what’s the point of having them without context? That’s why Discourse passes the relevant bits of context to each plugin outlet… but first, let’s take a step back. When you add this

```xml
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Your markup goes here, like...
  <h1>Hello World!!</h1>
</script>

```

It looks like HTML - and the script tags are - but what’s inside them is treated as [handlebars](https://handlebarsjs.com/) code.

That means that you can do something like this instead

```xml
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log this}}
</script>

```

and check the browser console. You’ll see this whenever the outlet is rendered; IE, you’re on a user page.

 ![image](https://global.discourse-cdn.com/meta/original/3X/2/4/247cb85f21022c5a22d9f93834eff600d1d4683e.png)

Now, is any of this stuff helpful? Yes… but not at the moment. We’ll come back to this. Let’s take another step back and see how Discourse passes the context to the outlet. If you search for the outlet name on Github - or locally - you’ll get this.

[Repository search results · GitHub](https://github.com/discourse/discourse/search?q=%22above-user-profile%22)

Let’s open that file. The first thing you see is this line

> <https://github.com/discourse/discourse/blob/d51d94f771cc5912374a24c030f6965fdacbcb7a/app/assets/javascripts/discourse/app/templates/user.hbs#L1>

Look at the last bit of that line

```plaintext
args=(hash model=model)

```

You’ll see that Discourse passes `model` as an argument to the outlet. For all intents and purposes and to keep things simple, `model = data`

So, one of the arguments for our outlet is `model`, and that’s where the data we want is going to be. So, let’s go back to our snippet.

```xml
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log this}}
</script>

```

and let’s change it to this instead

```diff
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
- {{log this}}
+ {{log args}}
</script>

```

Now we get this in the console.

 ![outlet arguments](https://global.discourse-cdn.com/meta/original/3X/e/3/e3ccc5a2990b62072c002a9a1d87a5fceee78344.png)

You can browse through that data and see if it has what you need. It should since it contains all the data about the user used in other elements on that page. It’s the “model” for the user page for that particular user.

One of the properties available there is… _drumroll_ 🥁 … the groups the user belongs to.

So, if you do

```plaintext
{{log args.model.groups}}

```

you’ll get all the groups the user belongs to in the console.

 ![user groups logged in the console](https://global.discourse-cdn.com/meta/original/3X/b/1/b1d2b3b5508716178597d572da2d18f376f4719e.png)

Alright, now we have the data we need, so the only thing left is to add some condition(s) based on that.

You might be tempted to think that we can do that in the same snippet, but, unfortunately, we can’t. Handlebars is a templating language. It has very, _very_ basic support for logic - nothing beyond simple true/false conditions and loops. You can’t do comparisons and other stuff like that.

So where exactly can you do that? In a connector class, sounds fancy… I know.

In a nutshell, a connector class is essentially a bit of Javascript attached to the outlet. It’s a lot more nuanced than that, but that’s all you really need to know for now.

So, let’s create one. We do it like this

```xml
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('OUTLET_NAME', 'SOME_NAME', {

});
</script>

```

`OUTLET_NAME` and `SOME_NAME` here **should be the same as the ones we used above**. So let’s change them

```xml
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {

});
</script>

```

This snippet goes in the `common > header` tab in your theme as well. So you should now have something that looks like this

```xml
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log args.model.groups}}
</script>

<script type="text/discourse-plugin" version="0.8">
  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {

  });
</script>

```

Inside our connector class, we can do some work… but… we need to be mindful that it’s not just like any javaScript file. For lack of a better description… think of it as an Ember component on a diet. Expanding on this is a bit outside the scope here, so let’s move on.

There are four methods wired up to it by default

> <https://github.com/discourse/discourse/blob/9ca93f57cce1f9a9a7e482fd1c15b5e9af288fab/app/assets/javascripts/discourse/app/lib/plugin-connectors.js#L20-L25>

actions allows you to define actions like so

```javascript
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  actions: {
    myAction() {
      // do something
    }
  }
});

```

You can then call that action from within the outlet, like when a button is pressed. We won’t be needing this one here, so let’s move on.

```javascript
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  shouldRender(args, component) {
    // return true or false here
  }
});

```

We also won’t be using this one since the outlet only renders on profile pages, and we don’t have any other requirements for now. However, you can use this to add any conditions you want to test before the outlet is rendered. For example, the trust-level of the current user or stuff like that. Moving on…

```javascript
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    // do something
  }
});

```

This is the one we want to focus on. Whatever javaScript conditions or variables you want to set go here. Before we dig deeper into that one, let’s cover the last method first for completeness’ sake

```javascript
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  teardownComponent(args, component) {
    // do something
  }
});

```

this fires when the outlet is going to be removed. So, it allows you to do any cleanup required, like remove event listeners and so on.

Ok, let’s go back to `setupComponent`

```javascript
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    // do something
  }
});

```

You can see that it has two things passed to it. First, there’s `args` and then there’s `component`

`args` here is the same stuff we looked at earlier. It’s the context data that Discourse passed to the outlet. So, if you do

```javascript
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    console.log(args.model.groups);
  }
});

```

you’ll see the same information in the browser console that we saw before. The groups that the profile owner belongs to. This is where the fun begins, you now have the data, and you have the correct hook. So you can do whatever you want here. So, if I want the video to only show on the profiles of members that belong to a certain group, I can do this

```javascript
  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;

      console.log(showVideo);
    }
  });

```

If you try this on a profile page that belongs to a user in the `staff` group, it will print `true` in the console. So, now the only thing we have left to do is to pass that to the outlet template. Here’s how you can do that.

`component` passed to `setupComponent` here is shared between the connector and the outlet. You can pass things to the outlet by setting them as properties on the component like so

```diff
  const TARGET_GROUP = "staff"

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;
- console.log(showVideo);
+ component.setProperties({showVideo})
    }
  });

```

Now, if we head back to the template and do something like

```plaintext
{{log showVideo}}

```

and it will print the same result. So we now put that in a Handlebars condition and add your markup inside it like so

```xml
<script
  type="text/x-handlebars"
  data-template-name="/connectors/above-user-profile/add-profile-videos"
>
  {{#if showVideo}}
    <video playsinline autoplay muted loop id="myVideo" poster="[INSERT LINK]">
  	  <source src="[INSERT LINK]" type="video/webm">
  	  <source src="[INSERT LINK]" type="video/mp4">
    </video>
  {{/if}}
</script>

```

then check a profile page for a staff user. You’ll see that it loads the video.

Once you navigate away from the staff member’s profile, the video will go away. The video won’t show on profiles for users who are not in the `staff` group.

So, let’s put all of this together. This is the same stuff from above.

Here’s the CSS I used. `common > css` tab

```scss
#myVideo {
  position: fixed;
  top: var(--header-offset);
  min-height: 100vh;
  left: 0;
  z-index: -1;
}

.user-content {
  background: none;
}

.user-main {
  padding: 0.5em;
  background: rgba(var(--secondary-rgb), 0.8);
}

// if you want it on mobile too
.mobile-view {
  body[class*="user-"] {
    background: none;
    .user-main,
    .user-content {
      padding: 0.5em;
      background: rgba(var(--secondary-rgb), 0.8);
    }
  }
}

```

HTML / javaScript / Handlebars. This goes in the `common > header` tab of your theme

```xml
<script
  type="text/x-handlebars"
  data-template-name="/connectors/above-user-profile/add-profile-videos"
>
  {{#if showVideo}}
    <video playsinline autoplay muted loop id="myVideo" poster="[INSERT LINK]">
  	  <source src="[INSERT LINK]" type="video/webm">
  	  <source src="[INSERT LINK]" type="video/mp4">
    </video>
  {{/if}}
</script>

<script type="text/discourse-plugin" version="0.8">
  const TARGET_GROUP = "staff"

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;
      component.setProperties({showVideo})
    }
  });
</script>

```

Change `TARGET_GROUP` to the name of the group you want to target and add the `src` attributes for your videos.

This post was on the longer side… don’t be deterred by that. Once you grasp the concept, everything we did above can be done in less than 3-5 minutes.

The nice thing here is that all the stuff we talked about is pretty much the same for _any_ plugin outlet. The only thing that changes is the name. So, this applies to any plugin-outlet modifications you want to do in the future.

1. find the outlet name
2. get the data
3. process the data in a connector
4. pass the properties back to the template

---

<div class="post-metadata">

### Author: ![orangeandblack5](https://avatars.discourse-cdn.com/v4/letter/o/edb3f5/32.png) [@orangeandblack5](https://meta.discourse.org/u/orangeandblack5)
#### Post date: [December 10, 2021, 10:38am UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916/5 "2021-12-10T10:38:42Z")

</div>

That is incredibly in-depth and I’ll be sure to look it over when I have some time next week, but suffice to say from a skim it seems much better than my current implementation (embed the video on _every single page_ and only show it on the user profile, which I accomplished with a script that adds a tag to the body of a user’s page if their account name is a certain thing). Thanks for the in-depth explainer, can’t wait to get around to it!

---

<div class="post-metadata">

### Author: ![orangeandblack5](https://avatars.discourse-cdn.com/v4/letter/o/edb3f5/32.png) [@orangeandblack5](https://meta.discourse.org/u/orangeandblack5)
#### Post date: [February 13, 2022, 8:28pm UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916/6 "2022-02-13T20:28:40Z")

</div>

Okay, so, for the most part, this approach works great, and the explanation is fantastic. Thank you very much for going above and beyond.

Working on a per-user basis is simple - we just check for a given username like so:

```plaintext
      const isUser1 = args.model.username == "User1"
      component.setProperties({isUser1})

```

However, we do run into an issue with our CSS - we wish to make some changes to how the user page looks for _only_ these users.

At the moment, we’re accomplishing that via the same code as before:

```plaintext
<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() =>{
        window.onload = determineUser();
    });
    
    async function determineUser() {
        await sleep(50);
        var pageURL = document.querySelector("meta[property='og:url']").getAttribute("content");
        var isUserPage = pageURL.includes("https://www.siteurl.com/u/");
        var isUser1 = pageURL.includes("u/User1/");
        document.body.className = document.body.className.replace(" user-page-animated","");

        
        if(isUserPage)
        {
            if(isUser1)
            {
                document.body.className += ' user-page-animated';
            }
        }
    }
    
    function sleep(ms)
    {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
</script>

```

This allows us to simply copy and paste the “User1” code for each new user, but relies on a 50ms delay after every page load before firing, which is visible to the end user (and, if removed, doesn’t work for whatever reason).

Is there any way to also hook this addition of a class to the body into the code you provided, so we can use it to style pages with videos differently from those without?

---

<div class="post-metadata">

### Author: ![orangeandblack5](https://avatars.discourse-cdn.com/v4/letter/o/edb3f5/32.png) [@orangeandblack5](https://meta.discourse.org/u/orangeandblack5)
#### Post date: [February 13, 2022, 8:40pm UTC](https://meta.discourse.org/t/add-background-video-to-certain-user-profiles/210916/7 "2022-02-13T20:40:52Z")

</div>

And, seriously, thanks again for the detailed explanation.
