0

I'm building an audio website.

It uses custom components (for the tracklists, the tracks, the track sources, the player..., but I'm stuck on something.

When the user clicks on a track, the track HTML eventually needs to be refreshed - this allows me, among others, to query the database for informations on that track (eg. track sources) that would be too long to load at the initialization.

Thus, I need to replace my track node by the updated HTML.

But I only find documentation about replacing the content of a node (.innerHTML), not the node itself. This doesn't work for me since I need to get the attributes of the new node.

I don't want to delete the old node and add the new one at the same place because I need to keep the reference to the first node.

What I want to achieve (simplified)

JS

<?php
class myCustomEl extends HTMLElement{
    constructor() {
        super(); //required to be first
    }
    connectedCallback(){
        this.render();
    }

    disconnectedCallback(){
    }
    attributeChangedCallback(attrName, oldVal, newVal){
    }
    adoptedCallback(){
    }

    static get observedAttributes() {
    }

    ///
    ///

    render(){
    }

    reload(){
        var self = this;
        var success = $.Deferred();

        /*
        Here we would make an ajax request to return the new content
        */
        var newContent = '<my-custom expires="XXXX">New Content</my-custom>';

        success.resolve();
        return success.promise();
    }

}

$( document ).ready(function() {

    $('my-custom').on('click', '.wpsstm-source-title', function(e) {
        var mynode = this;
        mynode.reload().then(
            function(success_msg){
                console.log("RELOADED!");
                console.log(mynode); //here I would like to be able to get mynode with its updated content
            },
            function(error_msg){
                console.log(error_msg);
            }
        );
    });
});

window.customElements.define('my-custom', myCustomEl);

HTML

<my-custom expires="XXXX">Old Content</my-custom>

What I actually do

(because I can't get it to work)

  • copy the new node .innerHTML to the old node .innerHTML,
  • remove all the attributes of the old node,
  • copy all the attributes of the new node to the old node.

It seems to work but I think it's quite hackish, and was wondering how I could achieve this differently.

function swapNode(oldNode,newHTML){

    //create new node from HTML
    var template = document.createElement('template');
    newHTML = newHTML.trim(); // Never return a text node of whitespace as the result
    template.innerHTML = newHTML;
    var newNode = template.content.firstChild;

    //check both nodes have the same tag
    if (oldNode.tagName !== newNode.tagName){
        console.log("wpsstmSwapNode - tags do not match, abord.");
        return false;
    }

    //remove all old attributes
    while(oldNode.attributes.length > 0){
        oldNode.removeAttribute(oldNode.attributes[0].name);
    }

    //add new attributes
    let attr;
    let attributes = Array.prototype.slice.call(newNode.attributes);
    while(attr = attributes.pop()) {
        oldNode.setAttribute(attr.nodeName, attr.nodeValue);
    }

    //switch HTML
    oldNode.innerHTML = newNode.innerHTML;

    return true;

}

I also tried this

var parent = self.parentNode;
var newContent = '<my-custom>NEWCONTENT</my-custom>';
var newNode = $(newContent).get(0);

var oldNode = parent.removeChild(self);
parent.appendChild(newNode);
newNode.appendChild(oldNode);

Thanks !

gordie
  • 1,637
  • 3
  • 21
  • 41
  • Please show us some code, with a before/after example of what you're wanting to accomplish. A concrete example would make this easier to understand. –  Mar 12 '19 at 21:15
  • 1
    You want to replace a node but not delete it? Maybe i'm crazy but once you replace a node the old one is deleted. – Mark Baijens Mar 12 '19 at 21:16
  • 2
    https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML – ry4nolson Mar 12 '19 at 21:16
  • As @ry4nolson said, set the `outerHTML` property of the custom element node you don't want to change, making sure you are setting the property on the DOM element and not on a JQuery object. – traktor Mar 12 '19 at 22:26
  • @MarkBaijens If there are references to a node/element (for example in a JavaScript global variable) then it's not actually deleted, it can be added back to the document. This actually is a useful feature in certain situations, if you're mindful that any event listeners are cleared (it works really good in conjunction with global listeners though). – Jonathan Gray Mar 13 '19 at 00:17
  • There shouldn't be any reason why either `.innerHTML` or `.outerHTML` can't work for you. The excuse that you "need to get the attributes of the new node" shouldn't apply since there is nothing preventing you from obtaining that information. The other excuse that you "need to keep the reference to the first node" also shouldn't apply since again there is nothing preventing you from obtaining a reference and keeping it post-removal. There is also no legitimate reason why you should even need to keep such a reference. – Jonathan Gray Mar 13 '19 at 00:31
  • You should provide some code. What you've added is useless because it doesn't show how/which custom elements and nodes are updated and rendered. – Supersharp Mar 13 '19 at 12:42
  • If that is what you really need to do there is a huge design smell here. – connexo Jan 08 '22 at 12:06

2 Answers2

0

Detach from the parent, add a new child node to the parent for what you want then add what you detached as a child to the new node.

Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
  • Thanks, might I ask for more informations ? It's quite theorical for me :) – gordie Mar 12 '19 at 21:59
  • What more do you want? – Adrian Brand Mar 12 '19 at 22:00
  • I don't get it, like this ? https://pastebin.com/AQZSEczT It just deletes my node ... – gordie Mar 12 '19 at 22:21
  • 1
    @gordie That code should be *in your question*. Maybe its there, but I can't spot it. –  Mar 12 '19 at 22:47
  • I can't see it as my work proxy is blocking pastebin – Adrian Brand Mar 12 '19 at 22:48
  • @AdrianBrand I can edit it into the bottom of the question. One moment. –  Mar 12 '19 at 22:57
  • @AdrianBrand: I'll try to explain better. What I want is to have something like mytrack.reload().then()... But if I remove the mytrack node and insert a new one at the same place; how can I access the new node inside the then() function ? Since mytrack is now the "old" node and will not refer to the new node. Or am I wrong ? Thanks ! – gordie Mar 13 '19 at 21:28
0

At first I thought you wanted something like the raw workings of jQuery.replaceWith.

Essentially using node.replaceChild with a copy of your target a la Creating a new DOM element from an HTML string using built-in DOM methods or Prototype.

var content = target.outerHTML.trim();
// manipulate content...
var replacement = document.createElement('div');
replacement.innerHTML = content;

target
    .parentNode
    .replaceChild( replacement.firstChild, target );

But if you don't want to replace the existing node itself (which is basically what manipulating outerHTML would do), then you have to either extract the parts you want to keep and append them to the replacement, or vice versa and extract the parts from the replacement and merge them to the original.

drzaus
  • 24,171
  • 16
  • 142
  • 201