Forum Discussion

Hoekstra_VFZ's avatar
3 years ago

Happy Holidays - Christmas hats for all users

So I made this thing to give all users a random christmas hat out of a set of 8


This is the code of the component (I placed the component in the header quilt)

<#-- custom vfz-js-christmas-hats -->

<@compress single_line=true>
<@liaMarkupCache ttl="60000" variation="node" anonymousOnly="false" />
<#import "theme-lib.common-functions.ftl" as commonUtils />
<#assign query = "SELECT rank.name FROM users WHERE id = '${user.id}' LIMIT 1" />
<#assign result = commonUtils.executeLiQLQuery(query) />
</@compress>

<#if result?has_content>
</#if>

<style>
@media screen and (min-width: 767px) and (max-width: 991px) {
	.lia-quilt-row-header-hero-top .custom-community-hero-left .vfz-hat{
		margin-top: -156px !important;
	}
}
</style>

<script>

window.addEventListener('DOMContentLoaded', (event) => {

function getImageHashAndConvertTo4digits(str) {
    const match = str.match(new RegExp(('image-id') + "(.*)" + ('image-dimensions'))) || str.match(new RegExp(('avatar-name') + "(.*)" + ('avatar-theme'))) || str.match(new RegExp(('images') + "(.*)" + ('ziggo'))) || str.match(new RegExp(('avatars') + "(.*)" + ('.')));
    var result = match[1].replace('a','0');
	result = result.replace('b','1');
	result = result.replace(/\D/g,'');
    result = result.substr(-4);
    result = parseInt(result);
    return result;
}

function setHatType(number) { // 5 6 1 2 7 8 3 4  
	var hatType = 0;
	hatType = (number % 2  == 0) ? (hatType + 1) : (hatType + 2);
	hatType = ((Math.floor(number / 10)) % 2  == 0) ? (hatType + 3) : (hatType - 1);
	hatType = ((Math.floor(number / 1000)) % 2  == 0) ? (hatType + 1) : (hatType + 3);
	var transform = ((Math.floor(number / 100)) % 2  == 0) ? "" : 'transform="scale(-1,1)"';
    const hatUrl = '/html/assets/christmashat_type'+ hatType +'.svg';
	return [hatUrl, transform];
}

function getPosition(target) {
    const position = target.getBoundingClientRect();
    return {
        left: position .left + window.scrollX,
        top: position .top + window.scrollY
    }
};

function placeHatOnCLass(query_str, parent_str) {

    var targetElements = document.querySelectorAll(query_str);
        
	if (targetElements) {
		var targetElementsLength = targetElements.length;
		while(targetElementsLength--) {
			var targetImage = targetElements[targetElementsLength].getElementsByTagName("img")[0];
            if (targetImage) {
				if (targetImage.className != 'vfz-hat' && targetImage.parentNode.parentNode != 'vfz-hat') {
					const imageHash = getImageHashAndConvertTo4digits(targetImage.src);
					const hatType = setHatType(imageHash);
					var overlay = document.createElement('img');
					overlay.className = 'vfz-hat';
					overlay.src=hatType[0];
					overlay.style.position = 'absolute';
					overlay.style.borderRadius = "initial";
					overlay.style.objectFit = "initial";
					overlay.style.boxShadow = "initial";
					overlay.style.zIndex = "2";
					targetImageSize = targetImage.offsetWidth;
					overlay.style.width = targetImageSize*1.8 + 'px';
					overlay.style.maxWidth = targetImageSize*1.8 + 'px';
					overlay.style.height = targetImageSize*0.9 + 'px';
					overlay.style.maxHeight = targetImageSize*0.9 + 'px';
					overlay.style.marginLeft = '-' + targetImageSize/2.4 + 'px';
					if (targetImage.parentNode.parentNode.className == 'custom-community-hero-welcome custom-community-hero' && window.innerHeight > 767 && window.innerHeight < 990) {
						overlay.style.marginTop = '-' + 0 + 'px';;
					} else {
						overlay.style.marginTop = '-' + targetImageSize/2.4 + 'px';
					}
					if (targetImage.parentNode.parentNode.parentNode.parentNode.className == 'lia-quilt-column-alley lia-quilt-column-alley-left' || targetImage.parentNode.parentNode.parentNode.parentNode.className == 'userColumn lia-data-cell-primary lia-data-cell-text') {
						targetImage.parentNode.parentNode.parentNode.insertBefore(overlay, targetImage.parentNode.parentNode);
					} else {
						targetImage.parentNode.insertBefore(overlay, targetImage);
					}
				}
			}
		}
	}
}

function initiateHats() {
    <#--<#if coreNode.id == 'phxsb37772'>placeHatOnCLass('.custom-community-hero-left');</#if>-->
    <#if page.name != 'GroupHubPage'>placeHatOnCLass('.lia-message-view-node-activity-message .lia-user-avatar');</#if>
    placeHatOnCLass('.lia-avatar-editable .lia-link-navigation'); 
    placeHatOnCLass('.lia-quilt-row-main-top .lia-user-avatar');
    placeHatOnCLass('.custom-featured-post-tile .lia-link-navigation');
    placeHatOnCLass('.lia-user-info-group .lia-link-navigation'); 
    placeHatOnCLass('.lia-message-view-idea-message-item .lia-user-avatar');
    placeHatOnCLass('.lia-thread-reply .lia-user-avatar');
    placeHatOnCLass('.lia-quilt-forum-message .lia-user-avatar');
    placeHatOnCLass('.UserNavigation .lia-link-navigation');
    placeHatOnCLass('.message-list .lia-link-navigation');
    placeHatOnCLass('.custom-message-list .lia-link-navigation');
}

function refreshHats() {
	placeHatOnCLass('.lia-message-view-node-activity-message .lia-user-avatar');
    placeHatOnCLass('.lia-avatar-editable .lia-link-navigation'); 
    placeHatOnCLass('.lia-quilt-row-main-top .lia-user-avatar');
    placeHatOnCLass('.custom-featured-post-tile .lia-link-navigation');
    placeHatOnCLass('.lia-user-info-group .lia-link-navigation'); 
    placeHatOnCLass('.lia-message-view-idea-message-item .lia-user-avatar');
    placeHatOnCLass('.lia-thread-reply .lia-user-avatar');
    placeHatOnCLass('.lia-quilt-forum-message .lia-user-avatar');
    placeHatOnCLass('.UserNavigation .lia-link-navigation');
    placeHatOnCLass('.message-list .lia-link-navigation');
    placeHatOnCLass('.custom-message-list .lia-link-navigation');
}

initiateHats();
setTimeout(refreshHats, 1000);
setTimeout(refreshHats, 1300);
setTimeout(refreshHats, 1600);

	
document.addEventListener('click', function(e) {
    if(e.target && e.target.id== 'custom-loader-button') {
        //console.log('custom-loader-button pressed');
		//refreshHats();
    }
});

var observer = new MutationObserver(function (mutations) {
    <#if page.name != 'GroupHubPage'>
		if (mutations[0].addedNodes[0]) {
			if (typeof mutations[0].addedNodes[0].className != "undefined") {
				if (mutations[0].addedNodes[0].className != 'vfz-hat') refreshHats();
			}
		}		
		if (mutations[0].addedNodes[1]) {
			if (typeof mutations[0].addedNodes[1].className != "undefined") {
				if (mutations[0].addedNodes[1].classList.contains('custom-message-tile')) refreshHats();
			    //if (mutations[0].target == 'div.message-list') refreshHats();
			}
		}
	</#if>
	<#if page.name == 'GroupHubPage'>
		if (mutations[0].addedNodes[0]) {
			if (typeof mutations[0].addedNodes[0].className != "undefined") {
				if (mutations[0].addedNodes[0].classList.contains('lia-panel-message')) refreshHats();
			}
		}
	</#if>   
});

observer.observe(document.body, { childList: true, subtree: true }); //attributes: true, characterData: true

});

</script>

<#-- /custom vfz-js-christmas-hats -->

 

These are the assets:

https://community.ziggo.nl/html/assets/christmashat_type1.svg

https://community.ziggo.nl/html/assets/christmashat_type2.svg

https://community.ziggo.nl/html/assets/christmashat_type3.svg

https://community.ziggo.nl/html/assets/christmashat_type4.svg

https://community.ziggo.nl/html/assets/christmashat_type5.svg

https://community.ziggo.nl/html/assets/christmashat_type6.svg

https://community.ziggo.nl/html/assets/christmashat_type7.svg

https://community.ziggo.nl/html/assets/christmashat_type6.svg

 

Feel free to use and improve all of this. Please share improvements as I had to implement a lot of dubious workarounds to get it to work. For instance on the GroupHubPage there is some DOM event listener from Khoros that removes all nodes inside the node of the avatar img. So the hats are removed. I had to make a timer to run the function again after the removal event. Does anyone know why Khoros does this??

 

 

  • AndrewF's avatar
    AndrewF
    Khoros Oracle

    Hoekstra_VFZ wrote:

    For instance on the GroupHubPage there is some DOM event listener from Khoros that removes all nodes inside the node of the avatar img. So the hats are removed. I had to make a timer to run the function again after the removal event. Does anyone know why Khoros does this??


    I see you are using a DOMContentLoaded listener, but for JS like this, try using the liaAddScript macro, which should place your JS at the bottom of the body and also help to ensure it runs after other late page setup work.

     

    A performance concern: the MutationObserver on document.body is firing when any element is added to the body without className === "vfz-hat", which could happen thousands of times on some pages. Each time, it does a fair amount of work and does not detect the hat(s) previously added, so it adds another hat. In my test on a profile page, I saw 70 img.vfz-hat for 10 user icons. These overlapping hats and the work to manage them could cause some performance issues for many users. I suggest to place breakpoints & logging in placeHatOnCLass to ensure it is only being called on those images that do not already have a hat displayed.

    As community DOM is very complex and varied across pages, and it can also be very dynamic, this is a case where rigid structure assumptions (parentNode, className instead of classList, indexed access like [0]) can be brittle, and using the built-in jQuery instance can be a lifesaver. For example, this check will determine if an element or any parent has the vfz-hat class:

    if ($(element).closest('.vfz-hat').length)

    For figuring out which images need hats, you can bake the whole search into the initial selector (even without jQuery) :

     

    // The current implementation loops over all querySelectorAll(".lia-user-info-group .lia-link-navigation") and then over getElementsByTagName("img") and then looks around for ".vfz-hat" which it can't find because the class is on a sibling of an ancestor. This one-liner finds them all:
    
    // jQuery
    $(".lia-user-info-group .lia-link-navigation img:not(.vfz-hat + * img)")
    
    // DOM
    document.querySelectorAll(".lia-user-info-group .lia-link-navigation img:not(.vfz-hat + * img)")