avi dahan
avi dahan

Reputation: 559

Calling object methods without "this" prefix

I'm refactoring a small project of mine into an ordered class like structure using object and all methods as prototypes. it's quite a messy code but the principal is quite simple , i'm creating an object constructor that sets all necessary props on init , and outside i'm defining all the methods via ObjectName.prototype.funcname

now inside each method if i would like to call another method i'm using "this.methodname()" and if i would like to access the object properties again i'm using "this.someprop" thats works perfectly but when specific cases occur i'm actually creating another object (not via the constructor because i don't need so i just need to store some properties ) and than i would like to call another method with the new object as "this" so i'm calling this.someMethod.call(newobj) but i'm messing everything because now if the method tries to call a different method it will fail but if it will access a property it will succeed. My goal here is to store the new Obj inside the main obj (created via the constructor ) as a "nested" object and if some conditions are met than call some methods with the new "nested" object as the this and still be able to call my main obj methods. Any best practise for that? My first guess was to add a param to the constructor so if i need another "nested" object i can call the constructor with this param and just get a small object instead of the "big" one that also call some methods.

Edit: I have got my constructor that looks something like that:

function IframeMain (IframeNode,Html){
    this.IframeDomReady(IframeNode);
   Object.assign(this,this.CreateContext(window.location.href,IframeNode));
    this.ParsedDoc = this.HtmlParser(Html);
    this.DocReady = false; 
    this.Appender(this.ContentDocument.head,this.ParsedDoc.Head).then(data => {
        this.Appender(this.ContentDocument.body,this.ParsedDoc.Body)
    }).catch(err => {
        console.error(err);
    });     
}

i'm calling "Object.assign" to merge my new object (this) with an object returned from "CreateContext" method. after that i'm taking some html and appending it to an iframe supplied to the constructor, now in some cases there is another iframe inside the html so i'm appending it and than i need to create a new object with this iframe contentWindow/Document so i can append html to the nested iframe, so what do you think is best practise here? if i would create inside the main object a nested one and call the constructor with some param to only return part of the main object and populate it?

CreateContext method:

IframeMain.prototype.CreateContext = function (src,IframeNode){
    if(!IframeNode.contentWindow || !IframeNode.contentDocument || !IframeNode.contentWindow.document)console.error("Context Problem!");

    return {
        NodesArray : [],
        Natives: {},
        DocReady: null,
        NestedContexts : {},
        IframeNode: IframeNode,
        ContentWindow : IframeNode.contentWindow,
        ContentDocument: IframeNode.contentDocument || IframeNode.contentWindow.document,
        Src : src,
        Location: this.ParseUrl(src),
    };

};

HtmlParser Method:

IframeMain.prototype.HtmlParser = function(HtmlString){
let doc = new this.ContentWindow.DOMParser().parseFromString(HtmlString, 'text/html');
//Some more code...... and return parsed html

Appender method:

IframeMain.prototype.Appender = async function(target,nodes){
// append each node recursively to the iframe document 
//More calls here to different methods and if we hit an iframe we create another context and i would like to call the exact same methods and not change a thing because on each method i'm using "this" so technically i can do this recursively for 1000 nested iframes.
so what do you think is best practise here?  

I'm creating a new instance like so:

Let Iframe = new IframeMain(iframeel,html);

and that's it the logic starts to execute immediately that way i tried to avoid creating another instance because it will execute it again and i don't need it.

when i encounter a new iframe i'm calling the :

IframeMain.prototype.nestedframes = async function(iframeNode,html,PrevSrc){

this.NestedContexts = this.NestedContexts || {};
this.NestedContexts[UID] = this.CreateContext(PrevSrc,IframeEl);
// more code ...
and then i would like to call my appender method like this:
 await this.NestedContexts[UID].Appender(this.NestedContexts[UID].ContentDocument.head,this.NestedContexts[UID].ParsedDoc.Head);

but obviously my created context doesn't contain those methods and if i will call my method like so:

await this.Appender.call(this.NestedContexts[UID],this.NestedContexts[UID].ContentDocument.head,this.NestedContexts[UID].ParsedDoc.Head);

// i will have problems in the appender method because my this is my new object but inside appender i can't call my methods because they doesn't exist for that obj.

Upvotes: 0

Views: 454

Answers (1)

Bergi
Bergi

Reputation: 664513

I'm still not quite sure, but I think what you are actually looking for is a separate Context class:

class Context {
    constructor(src, iframeNode) {
        if (!iframeNode.contentWindow || !iframeNode.contentDocument || !iframeNode.contentWindow.document) throw new Error("Context Problem!");
        this.nodesArray = [];
        this.natives = {};
        this.nestedContexts = {};
        this.iframeNode = iframeNode;
        this.contentWindow = iframeNode.contentWindow;
        this.ontentDocument = iframeNode.contentDocument || iframeNode.contentWindow.document,
        this.src = src;
        this.location = Context.parseUrl(src);
    }
    static parseUrl(src) { … } // if it needs properties of IframeMain, make it a method there
                               // and pass the parsed location to the Context constructor
    htmlParser(htmlString) {
        const doc = new this.ContentWindow.DOMParser().parseFromString(HtmlString, 'text/html');
        // Some more code...... and return parsed html
    }
    async append(target, nodes) {
        // append each node recursively to the iframe document 
        // More calls here to different methods and if we hit an iframe we create another context 
    }
    async nestedFrames(iframeNode, html, prevSrc) {
         const nested = this.NestedContexts[UID] = new Context(prevSrc, iframeEl);
         // more code ...
         await this.nestedContexts[UID].append(nested.contentDocument.head, nested.parsedDoc.Head);
    }
    // etc

i'm calling "Object.assign" to merge my new object (this) with an object returned from "CreateContext" method.

Don't do that. I think here is your main problem. Don't make contexts plain objects, make them class instances that have all the methods and properties that they need.

If your IframeMain does more than that minimal Context class, introduce a second kind of objects. Make a Context subclass, or probably better use composition.

class IframeMain {
    constructor(iframeNode, html) {
        this.IframeDomReady(IframeNode); // ???
        this.context = new Context(window.location.href, iframeNode);
        this.parsedDoc = this.context.htmlParser(Html);
        this.docReady = false;
    }
}

Not sure though what the exact difference between them would be. In your nestedFrames method you use the parsedDoc property of a context - if every context should have a parsed document, put that property there as well. Or if only the IframeMain is responsible for managing the collection of nestedContexts, put that there (instead of each context having own child contexts, in a tree-like structure).

Maybe I misjudged and you don't actually need two different data structures. If so, just keep the Context constructor minimal and don't call any method but only initialise properties. The work should be done in separate functions then:

 async function iframeMain(iframeNode, html) {
     const context = new Context(html, iframeNode);
     await context.append(context.contentDocument.head, context.parsedDoc.head);
     await context.append(context.contentDocument.body, context.parsedDoc.body);
 }
 try {
     const iframe = await iframeMain(iframeel, html);
} catch (err) {
    console.error(err);
}

Upvotes: 2

Related Questions