<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fred Christianson</title>
    <description>The latest articles on DEV Community by Fred Christianson (@devrelieffred).</description>
    <link>https://dev.to/devrelieffred</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1033840%2F616c790e-13d4-45c5-92b8-86a7592bbdbe.jpg</url>
      <title>DEV Community: Fred Christianson</title>
      <link>https://dev.to/devrelieffred</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devrelieffred"/>
    <language>en</language>
    <item>
      <title>Multi-window JavaScript App</title>
      <dc:creator>Fred Christianson</dc:creator>
      <pubDate>Tue, 28 Mar 2023 20:10:06 +0000</pubDate>
      <link>https://dev.to/devrelieffred/multi-window-javascript-app-326d</link>
      <guid>https://dev.to/devrelieffred/multi-window-javascript-app-326d</guid>
      <description>&lt;p&gt;&lt;em&gt;If you want to see this in action before continuing there are 2 examples &lt;a href="https://fredchristianson.github.io/javascript-examples/examples/index.html"&gt;fredchristianson.github.io&lt;/a&gt; deployed from my &lt;a href="https://github.com/fredchristianson/javascript-examples"&gt;GitHub examples repository&lt;/a&gt;. Use the "Simple Child Windows" or "Child Window" links on that page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I always knew window.open() could do more than I used it for.  But I never know how great it is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fbP9sN6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3fdw5veslwrnkbmwcal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fbP9sN6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3fdw5veslwrnkbmwcal.png" alt="Multi-window example app" width="880" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-window JavaScript App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm working on a project where I want to see multiple things at the same time&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Test controller&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Results &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visual test&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manual accept/fail of (3)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a multi-monitor system, I didn't want to squeeze all that into one browser window.  Separation can be nice even with one monitor.  So I decided to look more carefully at window.open().  I've used apps where a popup window form is obviously working with the main form so I knew it was possible.  Turns out it's extremely easy and powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terminology
&lt;/h2&gt;

&lt;p&gt;These are the terms I will use in this document&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parent&lt;/strong&gt; - the window that called window.open().  Most documents call this "opener", which is the property name in the opened window.  "Primary window" is also used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Child&lt;/strong&gt; - the window created by window.open().  I have seen this called a "secondary window".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name&lt;/strong&gt; - the 2nd window.open() parameter is called "target".  I will use the term "name" for child windows.  In my implementation name is used &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;to create the target (whitespace removed), &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;as the new window title, &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;can be used to manage multiple open windows (e.g. in a Map).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;used to save/load window position in localStorage (.e.g. child-window-pos-${name})&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;p&gt;Parent and child windows have access to each other's global "window" object&lt;/p&gt;

&lt;p&gt;Access includes window.document.body which allows usage of most common DOM properties, manipulation functions, and event listening on any element in the other window's body.&lt;/p&gt;

&lt;p&gt;window.postMessage() is an easy way to pass data/requests between windows. The other window needs to listen to "message" events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Window Creation
&lt;/h2&gt;

&lt;p&gt;window.open() takes up to 3 parameters, and returns a Window object (actually a proxy to a Window object)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const child = window.open(url, target, features)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If popups are blocked in the domain, this will fail.  It's not always obvious in the browser UI so it's good to alert("New window failed.  Are popups enabled?");&lt;/p&gt;

&lt;h3&gt;
  
  
  url
&lt;/h3&gt;

&lt;p&gt;The first parameter can be an absolute or relative URL or an empty string.  An empty string opens a window with a blank page.  If the URL is not an empty string, the browser attempts to load it.  &lt;/p&gt;

&lt;h3&gt;
  
  
  target
&lt;/h3&gt;

&lt;p&gt;This optional parameter can be null or any of the target values allowed in an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element: _self, _blank, _parent, and _top.  It can also be the name of a window to open (or reuse if a window of that name is already opened).  Whitespace is not allowed in the name.&lt;/p&gt;

&lt;h3&gt;
  
  
  features
&lt;/h3&gt;

&lt;p&gt;The last parameter is also optional or can be a comma-separated string of "name=value" pairs.  If no parameters are specified, the new window will probably open as a tab in the same browser window as the parent.  MDN has full details of this parameter.&lt;/p&gt;

&lt;p&gt;If the feature "popup=true" exists, the browser creates a "minimal popup window". The browser decides what "minimal" is.&lt;/p&gt;

&lt;p&gt;One or more of "width", "height", "left", "top" can be specified.  The values are in pixels, and the browser selects values for any that are not specified.  For example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"width=500,height=600,top=100"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates a window 500x600 pixels at a location with a top of 100 and a left selected by the browser.   The browser will ignore position values if they would place the window off screen.  See "Multi-monitors on Chrome" section below for details of using multi-display systems on Chrome.&lt;/p&gt;

&lt;p&gt;You can also specify "noopener" if the child should not have access to the parent's DOM and "noreferrer" if the URL request should not include the HTTP referrer header.   These are not usually useful in a multi-window application.  &lt;/p&gt;

&lt;p&gt;Google returns many sites referring to other features like "resizable" and "memubar". They are not in the current MDN docs and I do not see any effect in chrome or firefox.&lt;/p&gt;

&lt;h2&gt;
  
  
  window.open() return
&lt;/h2&gt;

&lt;p&gt;If window.open() succeeds, it will return a Window object for the window where it was opened. This object is the same class as the global "window" value, but a different instance. If open failed it returns null. The main cause of failure is having popups blocked.  &lt;/p&gt;

&lt;p&gt;The returned child window has not loaded the URL at this point.  It does not have a document (so no body or elements to work with)  You need to wait for a "load" event.  A simple way to do that is with an async function like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function openChild(url,target){
    const child = window.open(url, target);

    return new Promise((resolve, reject) =&amp;gt; {
        if (child == null) {
            alert("open failed.  are popups blocked?");
            reject();
        }
        child.addEventListener('load', async (_event) =&amp;gt; {
            resolve(child);
        });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One problem is that the HTTP request for the URL may fail.  There is no good way to tell if the request failed.  The "load" event only indicates the HTTP request is complete.  But not if it completed with a 200 status, or 404, or 500, or anything else.&lt;/p&gt;

&lt;p&gt;If you are loading a page with known content, you can query the child document body after "load" to determine if it has expected text or elements.  Or you can include javascript in the child page that uses window.opener.postMessage() to notify the parent that it is loaded and running.  (You can also use custom events, or manipulate the parent DOM instead of postMessage if that's more appropriate for your application).&lt;/p&gt;

&lt;p&gt;There is an "error" event the parent window can listen to.  Unfortunately, that is triggered by errors processing the returned URL, not by HTTP errors.  It may be useful, for example, to detect javascript initialization errors in the child windo.&lt;/p&gt;

&lt;p&gt;Assuming this is a coordinated multi-window application, it's probably best for the child window to include javascript that notifies the parent window when it is ready (see postMessage below).&lt;/p&gt;

&lt;p&gt;If the request can timeout you may want to use setTimeout to give up after some amount of time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return new Promise((resolve, reject) =&amp;gt; {
    let timeout = setTimeout(reject,5000); // 5 seconds
    child.addEventListener('load', async (_event) =&amp;gt; {
        clearTimeout(timeout);
        resolve(child);
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The parent window can easily close the child window&lt;/p&gt;

&lt;p&gt;childWindow.close()&lt;/p&gt;

&lt;p&gt;But a multi-window app needs to consider many complications&lt;/p&gt;

&lt;p&gt;The user may close the child window unexpectedly while the parent expects to still access its elements.&lt;/p&gt;

&lt;p&gt;The user may close the parent window while child windows are left.&lt;/p&gt;

&lt;p&gt;The child window may listen for "beforeunload" events and cancel the close&lt;/p&gt;

&lt;p&gt;The parent window may listen for "beforeunload" events and cancel the close&lt;/p&gt;

&lt;p&gt;None of these are difficult to solve, just things that should be considered when developing a multi-window application.&lt;/p&gt;

&lt;p&gt;1) "beforeunload" events are unreliable when it comes to canceling a close.  It is not a good idea for either the parent or child to use the event for that purpose.  &lt;/p&gt;

&lt;p&gt;2) When the application's main window is closed, it is usually a good idea to close all child windows.  This closes 2 child windows if they are not null&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// close the child windows when the main window is closed.
window.addEventListener('beforeunload', () =&amp;gt; {
    controlWindow?.close();
    drawWindow?.close();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) A parent can listen to a child's 'beforeunload' event and create a new child if the child window should not be closed.  This isn't as good as canceling the close, but is reliable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;child.addEventListener('beforeunload', recreateChild);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;recreateChild() would probably&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;save the child's position&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create a new child&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;set the new child's position to replace the closing child&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;restore the new child's state (input value, text, etc)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;listen for child events - including 'beforeunload'&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) A parent can use the child window's "closed" property to recreate a closed child when needed rather than during 'beforeunload'&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (child.closed) {
    recreateChild();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think it is useful to save the window's location in "beforeunload"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const location = {
  left: window.screenX
  top: window.screenY
  width: window.innerWidth
  height: window.innerHeight
};

localStorage.setItem('child-window-location',
                     JSON.stringify(location)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use that to generate features when opening the window again&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const location = localStorage.getItem('child-window-location');
let features = 'popup=true';
if (location != null) {
   features = `left:${location.left},...`
}
window.open(url,name,features); 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chrome needs permission for this to work with multiple monitors.&lt;/p&gt;

&lt;h2&gt;
  
  
  document
&lt;/h2&gt;

&lt;p&gt;A loaded child window has a document just like any other javascript window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const childDocument = childWindow.document;
const childBody = childDocument.body;

These can be used used to inspect, change, and listen to the child DOM the same as the main (parent) DOM

const mainBody = window.document.body;

mainBody.style.backgroundColor = '#00f';
childBody.style.backgroundColor = '#00f';

mainBody.querySelector('.message').append('new message');
childBody.querySelector('.message').append('new message');

mainBody.querySelector('input.name').value;
childBody.querySelector('input.name').value;

mainBody.addEventListener('mousemove',mainMouseMoved);
childBody.addEventListener('mousemove',childMouseMoved);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The child has similar access to the parent's DOM&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const parentBody = window.opener.document.body;

parentBody.style.backgroundColor = '#00f';

parentBody.querySelector('input.name').value;
parentBody.querySelector('.message').append('new message');

parentBody.addEventListener('mousemove',mainMouseMoved);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it is possible for JavaScript in multiple windows to use multiple DOMs, it will likely cause problems with debugging and maintaining the code.  The most maintainable solutions will be either&lt;/p&gt;

&lt;p&gt;The parent window handles all DOM management (modifications and events)&lt;/p&gt;

&lt;p&gt;The child window handles of its DOM management and the parent does nothing with childWindow.document.  In this case, the 2 parts of the application communicate with postMessage or CustomEvents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global Classes
&lt;/h2&gt;

&lt;p&gt;This doesn't come up often, but classes such as HTMLElement and HTMLDivElement are properties of Window.  And they are not the same in the parent and child window objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const parentDiv = window.document.createElement('div')
const childDiv = childWindow.document.createElement('div')

parentDiv instanceof HTMLElement;             // is true
childDiv instanceof HTMLElement;              // is false
childDiv instanceof childWindow.HTMLElement;  // true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my testing, even though parentDiv and childDiv are not created by the same HTMLElement constructor, they may be inserted into with DOM.  It is probably safest for the future to use the correct window's document.createElement to create new elements.  Different window objects also have different DOMParsers so use the correct one of those also.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global Values
&lt;/h2&gt;

&lt;p&gt;Classes are just one type of property a Window object contains.   They are just functions and any other function or data can be added to a parent or child window and accessed by the other&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parent JavaScript:
    window.callParent = function(...args) {...};
    window.parentData = { a:1,b:2};

child JavaScript:
    window.opener.callParent(1,2,['a','b']);
    const parentData = window.opener.parentData;
    parentData.a = 5;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, the child can add properties to its window and the parent can call or access them.&lt;/p&gt;

&lt;p&gt;This is probably a bad idea in most cases.   JavaScript debugger and console logs are not shared between the windows.  So it can be more difficult to track down problems if multiple windows are modifying the same data.  &lt;/p&gt;

&lt;p&gt;Child and parent JavaScript can communicate with postMessage and CustomEvents in a way that is likely to be much more maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inter-window Events
&lt;/h3&gt;

&lt;p&gt;The simplest multi-window application runs all JavaScript in a single window.  It has access to the DOM in every window and takes care of everything.   There are many cases where there are advantages to developing the windows independently (JavaScript, HTML, CSS) and use inter-window communication.  The communication may be modifying DOM, calling each other's functions, accessing window properties, or events.&lt;/p&gt;

&lt;p&gt;An architecture where windows communicate with each other through events is likely to be&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Most maintainable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Most extensible &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Most testable&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In terms of maintainability, the JavaScript, HTML, and CSS for each window can be changed without concern for the implementation of other windows.  The events make an API and as long as the API is followed problems are unlikely.&lt;/p&gt;

&lt;p&gt;For extensibility, a window implementation for one application may be more easily added to other applications.  The event API must be followed, but the DOM, data, and functions are independent.&lt;/p&gt;

&lt;p&gt;Testing a child window implementation is fairly straightforward with an event interface.  And the test harness will not need to change as the child implementation changes.&lt;/p&gt;

&lt;p&gt;There are 2 (very similar) ways for windows to communicate with events.  The first is custom events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Events
&lt;/h3&gt;

&lt;p&gt;JavaScript CustomEvents are listened to like any other event.  There are a few things to manage&lt;/p&gt;

&lt;p&gt;The sender and receiver need to use the same name ("myevent" below)&lt;/p&gt;

&lt;p&gt;The second parameter of the CustomEvent constructor must be an object with a "detail" property to send data to the listener&lt;/p&gt;

&lt;p&gt;dispatchEvent is used to send the event to the listener (dispatchEvent can be used on elements as well as on the Window object.  event.target has the window or element as with any other event)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parent: 
    window.addEventListener('myevent',(event)=&amp;gt;{
        const data = event.detail;
        // data is the detail object send by the Child
        // {"name":"fred","age":57} in this example
    });

Child:
    const data = {
        "name": "fred",
        "age": 57
    };
    const event = new CustomEvent("myevent",{detail: data});
    window.opener.dispatchEvent(event);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is fairly simple and works the same if the child listens to its window and the parent dispatches the event.  This works great for one-way events (i.e. notifications).  It needs more if the child needs a response.  In my opinion, the easiest is to use a Promise&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parent:
    window.addEventListener('myevent',(event)=&amp;gt;{
        const data = event.detail;
        if (isValid(data)) {
            const response = {"message":"got it", value:42};
            data.resolve(response);
        } else {
            const error = new Error("something went wrong");
            data.reject(error);
        }
    });

Child:
    let resolveEvent = null;
    let rejectEvent = null;
    let promise = new Promise((resolve,reject)=&amp;gt;{
        resolveEvent = resolve;
        rejectEvent = reject;
    })

    const data = {
        "name": "fred",
        "age": 57,
        resolve: resolveEvent,
        reject: rejectEvent
    };
    try {
        const event = new CustomEvent("myevent",{detail: data});
        window.opener.dispatchEvent(event);
        const response = await promise;
        // got response
    } catch(error) {
        // something failed
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is much more code for a single event with a response than a one-way communication.  But most of it is common and easily encapsulated in a module and would not need to be repeated if there are many events passed between windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  postMessage
&lt;/h3&gt;

&lt;p&gt;In a multi-window application on the same domain, postMessage has no advantage over custom events.   postMessage has checks for the "same-origin policy" so it would be preferred if that is a concern.  You can read more about it on mdn web docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-monitor Windows on Chrome
&lt;/h2&gt;

&lt;p&gt;Firefox, and most browsers support features with left and top on any monitor.  Chrome requires user permission or it always moves the new window to the same monitor.&lt;br&gt;&lt;br&gt;
It has a global function to ask the user for permission: getScreenDetails.&lt;/p&gt;

&lt;p&gt;I created a function to ask the user for permission and wait for a response if Chrome's function exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async _checkMultipleScreenPermission() {
    if (!('getScreenDetails' in window)) {
        return true;
    }
    const promise = new Promise((resolve, reject) =&amp;gt; {

        window.getScreenDetails()
            .then(() =&amp;gt; {
                //user allowed multiscreen
                resolve(true);
            })
            .catch((ex) =&amp;gt; {
                // user did not allow multiscreen.  not a problem.
                resolve(false);
            });
    });
    return await promise;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before using window.open with features that specify a left or top, call this function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await checkMultipleScreenPermission();
window.open(url,target,"left:2000,top:50");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If getScreenDetails doesn't exist, the await will return immediately and the window will be opened.  It will also return immediately if the user has already granted or denied permission, so will not prompt the user every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples and Discussion
&lt;/h2&gt;

&lt;p&gt;There are 2 examples of child windows on GitHub if you want to see any of this work&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/fredchristianson/javascript-examples/tree/master/examples/child-windows-simple"&gt;Simple Example&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/fredchristianson/javascript-examples/tree/master/examples/child-windows"&gt;Advanced&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to just see them in action they are deployed to &lt;a href="https://fredchristianson.github.io/javascript-examples/examples/index.html"&gt;fredchristianson.github.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The best place to contact me if you have corrections or questions is &lt;a href="https://twitter.com/DevReliefFred"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>Software Coupling (Part 2 – OO)</title>
      <dc:creator>Fred Christianson</dc:creator>
      <pubDate>Sun, 26 Feb 2023 02:55:53 +0000</pubDate>
      <link>https://dev.to/devrelieffred/software-coupling-part-2-oo-n78</link>
      <guid>https://dev.to/devrelieffred/software-coupling-part-2-oo-n78</guid>
      <description>&lt;p&gt;I provided a brief introduction to coupling in part 1. This article is specifically aimed at coupling in object-oriented systems.&lt;/p&gt;

&lt;p&gt;There are academic ways of Measuring Cohesion and Coupling of Object-Oriented Systems. And there are formal explanations of types of coupling and cohesion and complexity metrics.&lt;/p&gt;

&lt;p&gt;I’m ignoring academia and formality and measures. I’ll explain the types of relationships in object-oriented systems that create complexity. This complexity is often a large component of technical debt. I’ll refer to all types of relationships as coupling even when it’s not formally the correct term.&lt;/p&gt;

&lt;p&gt;There is a lot of theory on reducing coupling with tools like encapsulation, inheritance, and abstraction. I am writing about what I’ve experienced in decades of practice which does not always match the promises of theory.&lt;/p&gt;

&lt;h1&gt;
  
  
  Relationship Example
&lt;/h1&gt;

&lt;p&gt;I had a client with 100s of Java applications and 1000s of packages intended to be reused wherever needed. One of my tasks was to extend “App 1” (in the diagram) to include features from “Package B”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TFnSRwSq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5yzhdx0unxczk38k5rz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TFnSRwSq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5yzhdx0unxczk38k5rz.png" alt="Image description" width="471" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;B was used pretty heavily in many apps without issue, so this should be easy. But it wasn’t.&lt;/p&gt;

&lt;p&gt;To keep this short, A acted weird after B was added to App 1. And nobody knew much about C. And there were a dozen other packages that were just as likely to be the problem.&lt;/p&gt;

&lt;p&gt;We finally figured out that if there were no explicitly configurated properties for C, it chose defaults that were altered by an optional dependency injection that B provided.&lt;/p&gt;

&lt;p&gt;There are many ways this could have been avoided. And it could have been a reasonable implementation when C was created.&lt;/p&gt;

&lt;p&gt;Configuration issues are not a problem that is specific to object-oriented systems. This example is not meant to show OO or DI is bad. But that there are hidden relationships as well as explicit dependencies involved in the coupling among parts of the system. And also, because of the number of packages, classes, and interfaces used by the app, it’s unlikely that documentation of C would have helped to find the issue.&lt;/p&gt;

&lt;p&gt;chatGPT tells me&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To minimize coupling, developers can use techniques such as encapsulation, abstraction, and inheritance, which promote modularity and loose coupling between different parts of the system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While this is true, a little loose coupling can cause trouble. And a lot of loose coupling makes a system difficult to maintain and extend.&lt;/p&gt;

&lt;h1&gt;
  
  
  Interfaces
&lt;/h1&gt;

&lt;p&gt;Interfaces can be used to reduce coupling.&lt;/p&gt;

&lt;p&gt;In this diagram, ConsumerA only depends on the interface. In theory, any of the implementations can be provided to a ConsumerA object and it will work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CLs815UU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhkimfhczwlc9vqzo63z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CLs815UU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhkimfhczwlc9vqzo63z.png" alt="Image description" width="880" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In practice, there are many relationships in a code base with these items. There are many things that may cause problems in the future:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CsRrK3Zn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rbfcfn32rouwhh4v98l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CsRrK3Zn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rbfcfn32rouwhh4v98l.png" alt="Image description" width="720" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A necessary change to InterfaceA requires changes to all implementations&lt;br&gt;
A new implementation may follow the interface contract, but not the intent. It just happens to be an easy hook into consumers, so it is used.&lt;br&gt;
ImplB is modified and causes performance issues with consumers&lt;br&gt;
ImplC has side effects. It’s usually used for other things but a DI change causes it to be injected into a consumer which breaks because of the side effects.&lt;br&gt;
Interfaces are great and have many uses. Most will not cause problems, but in a project with dozens or hundreds of global interfaces, it’s easy to have a few that cause problems in version 2, or 3, or N.&lt;/p&gt;

&lt;p&gt;Interfaces can reduce coupling if used well. But a little thought of scope and functionality during architecture design can reduce the chance of future problems. It’s not a simple matter of “create an interface and coupling is reduced”.&lt;/p&gt;
&lt;h1&gt;
  
  
  Data
&lt;/h1&gt;

&lt;p&gt;Data is usually encapsulated so users of a class cannot access fields directly. While this provides some protection, it is a relationship that can break in the future.&lt;/p&gt;

&lt;p&gt;In this diagram, the Base class implements virtual methods getA() and setA(). SubclassB overrides them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_zdUu1gI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sq0tqoc3n6dahd2wuz5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_zdUu1gI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sq0tqoc3n6dahd2wuz5v.png" alt="Image description" width="607" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a few relationships that need to be considered:&lt;/p&gt;

&lt;p&gt;Type of field A: If this changes, it will require changes anywhere Base get/setA() is used.&lt;br&gt;
Implementation of SubclassB: this subclass may have (or add) constraints that break ConsumerA. If ConsumerA is given a Base object, the writer may not be aware that SubclassB.setA() is different than Base.setA()&lt;br&gt;
SubclassA: this subclass may be changed to override getA() and setA() with constraints or modifications. While there is no coupling at this time in the code, there is a relationship in the architecture that may cause issues.&lt;br&gt;
SubclassC: As with interfaces, future subclasses may be created which match the contract of getA() &amp;amp; setA() but have side effects or performance issues.&lt;br&gt;
When architecting a class hierarchy it’s good to consider why getters and setters are created. Do consumers really need access? Can the scope be limited (package, subclass)? I’ve heard arguments for including getters and setters&lt;/p&gt;

&lt;p&gt;It makes the class more flexible when consumers can access the data. Encapsulation protects the data so why not?&lt;/p&gt;

&lt;p&gt;While this is true, it also adds coupling now or potentially in the future. The class designer should determine what consumers will need and why. It takes more thought in the architecture/design phase but limits potential complexity to be intentional about data access.&lt;/p&gt;
&lt;h1&gt;
  
  
  Methods
&lt;/h1&gt;

&lt;p&gt;Methods have a similar coupling as data getters and setters, including future relationships from being virtual.&lt;/p&gt;

&lt;p&gt;Any method which is called from within a class, or a subclass, or an external class is a relationship. If it is not called but is within scope, it is a possible future relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1ZO67VR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39i8aoh3ih8oq2ywyqwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1ZO67VR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39i8aoh3ih8oq2ywyqwy.png" alt="Image description" width="582" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As with data, a broader scope makes the class more flexible since coders of external classes can decide when and how a method is used. A good class design considers not only why a method exists, but when, where, and how the method should be used. Leaving those decisions up to consumers leads to complexity and often misuse of classes which becomes difficult if not impossible to untangle.&lt;/p&gt;

&lt;p&gt;One way to significantly reduce virtual method complexity is to make all virtual methods protected. A common virtual method pattern is to make the virtual method public and use it in the subclass&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public override int methodA(int b)
    {
      var result = base.methodA(b);
      /* do more */
      return result;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a heavy coupling between consumers calling methodA and any implementation. It also allows a subclass to not call base.methodA() – intentionally or accidentally. Even if the base implementation does nothing now, in the future it may add validation or calculations, or any number of other functionality that it expects subclasses to call.&lt;/p&gt;

&lt;p&gt;It is better to have a base public methodA that is not virtual. It will always be in control and subclasses only need to implement what they need&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  internal class BaseB
  {
    private int a;

    public int methodB(int b) { 
      if (!isValid(b))
      {
        throw new ArgumentException();
      }
      setupB(b);
      doMethodB(b);  
      cleanupB(b); 
      return a; 
    }

    private bool isValid(int b) { return b &amp;gt;= 0; }

    protected virtual void setupB(int b) { }
    protected virtual void doMethodB(int b) { a = b; }
    protected virtual void cleanupB(int b) { }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks like more effort than a public methodB(). But it only needs to be implemented once. All subclasses are guaranteed to call&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;isValid&lt;/li&gt;
&lt;li&gt;setupB&lt;/li&gt;
&lt;li&gt;cleanupB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;in the correct order. Consumers are guaranteed that argument validation is called and that any necessary setup and cleanup is performed. Additional mandatory functionality can be added to the base class in the future without the possibility of subclasses not calling the base method. There are many other advantages that may come up over multiple versions of an application. It may only occur in a small percentage of classes, but it can be very helpful to add code to the base class and be guaranteed that it will be called for every subclass and in the right order. Some of these areas are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;resource management (open/close files, create/free drawing contexts)&lt;/li&gt;
&lt;li&gt;monitoring &amp;amp; metrics&lt;/li&gt;
&lt;li&gt;performance (timeout, caching, etc)&lt;/li&gt;
&lt;li&gt;synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With no public virtual methods, users of the class only have tight coupling to a single known base class. It’s possible for subclasses to cause problems, but it’s much less likely when they implement the minimal polymorphic functionality and the base class guarantees documented common functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency Injection
&lt;/h2&gt;

&lt;p&gt;Dependency Injection is very powerful for many things from testing to configurable runtime implementations. It also can create relationships and chains of relationships that neither the dependent class nor the dependency class has considered.&lt;/p&gt;

&lt;p&gt;It provides a simple way to create proxy classes to execute a method remotely or add validation, logging, or any number of other extensions without modifying the dependency class. The proxy class (or alternate implementation) can also add&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;performance issues&lt;/li&gt;
&lt;li&gt;stability issues&lt;/li&gt;
&lt;li&gt;security issues (logging a password)&lt;/li&gt;
&lt;li&gt;synchronization issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When DI is used to create architected configurations of components it is powerful. When it is used to put components together in a way the injector decides is appropriate it’s easy to end up with unknown relationships and complexity.&lt;/p&gt;

&lt;p&gt;The runtime architectural structure should be intentional. Stability is difficult when the structure is a result of many independent injection requests.&lt;/p&gt;

&lt;h1&gt;
  
  
  Client-Server
&lt;/h1&gt;

&lt;p&gt;It’s common to share components on a client and server. Whether its&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data structure&lt;/li&gt;
&lt;li&gt;class design&lt;/li&gt;
&lt;li&gt;class implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;it is very tempting to have both sides of an API use the same thing. This can happen with a web front end using a nodeJS API. Or with microservices calling each other in any language. It certainly speeds up development when the same component can be used on both sides.&lt;/p&gt;

&lt;p&gt;It’s easy to end up with one side not being architected – it just copies the other side. It’s unlikely that the architecture on both sides ends up as good as it could be. And it’s very likely that there will be future limitations and difficulties because of the tight coupling between the client and server.&lt;/p&gt;

&lt;p&gt;This is a specialization of the DRY principle. It’s important to look not just at the what and how, but why the functionality is needed. If the why is different, there is an unnecessary relationship that can be restrictive in the future.&lt;/p&gt;

&lt;p&gt;In general, data on the server exists for different reasons than the client (e.g. database CRUD vs UI). It is unlikely that the same classes will be appropriate as the system iterates through versions. It is likely that changes on one side will be limited by the other or cause problems on the other.&lt;/p&gt;

&lt;h1&gt;
  
  
  Avoidance
&lt;/h1&gt;

&lt;p&gt;Object-oriented systems have many relationships by design. One way to avoid system complexity is to localize useful relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Module &amp;amp; Package Interfaces
&lt;/h2&gt;

&lt;p&gt;Keywords like sealed, internal, final, protected, and private are often avoided because “they make the class/module/package less flexible”. But that flexibility is what leads to unmanageable complexity.&lt;/p&gt;

&lt;p&gt;The 2 class designs below have the same number of objects and modules. The one on the right has additional interface objects (rectangles) and all inter-module relationships are isolated to those interfaces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hAi4_mJ3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9g7uagi4hx3sa8sh4feu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hAi4_mJ3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9g7uagi4hx3sa8sh4feu.png" alt="Image description" width="720" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BzgaIP81--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejpe0joszypy9x4rbeyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BzgaIP81--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejpe0joszypy9x4rbeyv.png" alt="Image description" width="720" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The entire implementation inside of a module can be replaced without risk to other modules on the right design. While that is the idea behind interfaces, what simplifies this design is that the interfaces share data-only objects. There are no relationships between classes in one module and methods of classes in another.&lt;/p&gt;

&lt;p&gt;Modules are usually used to group related objects or functionality. They are less often used to isolate complexity, but that is where they are most powerful as an application iterates through multiple versions.&lt;/p&gt;

&lt;p&gt;While it’s rarely possible or even desirable to hide module function (i.e. public methods) it is useful to think about the minimal interface (data &amp;amp; methods) a module can expose and still meet its objective.&lt;/p&gt;

&lt;p&gt;Access can always be loosened later if necessary (e.g. protected–&amp;gt;public) fairly easily. Tightening access can be much more difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microservices
&lt;/h2&gt;

&lt;p&gt;I’ve seen microservices used, not to reduce complexity, but because changes made in one part of a monolithic service always break other parts. Instead of requiring all parts of the system to be debugged and deployed together, only the changed service needs to work.&lt;/p&gt;

&lt;p&gt;The microservice deployments are a bandaid over a complex monolithic system that is no longer maintainable. There will certainly be shared components of any system with microservices. The components need to have as few relationships as possible in order to avoid the same problems a tightly coupled monolithic system has.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Mappers
&lt;/h2&gt;

&lt;p&gt;Learn to love data mappers. In version 1 they may do nothing. Or may make a copy of an object.&lt;/p&gt;

&lt;p&gt;Once mapper infrastructure is in place, it is trivial for one component (class, module, client) to change its data structure without impacting others. Each component should use data structures that suit it best, and not make do with objects it is passed using data that is appropriate for another module.&lt;/p&gt;

&lt;p&gt;A data mapper may create a new object and copy/transform data from another object. Or it may be a wrapper around another object with restricted access or transformations on get/set. Or an interface that provides a limited view of fields. The most important value of a data mapper is that it encourages the developer to think through the data design for each module rather than using a system-wide entity definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  API (client-server)
&lt;/h2&gt;

&lt;p&gt;Architect the client and server independently. While the architectures may have a lot in common, the 2 systems serve different purposes and there should be differences in the architecture. This includes data, transformations, functionality, and many other things.&lt;/p&gt;

&lt;p&gt;It will be tempting to reuse components (e.g. classes) but think carefully about copy/paste instead of shared classes. For rapid prototyping without much architecture, it can be much quicker to share classes initially. But as soon as you have reached some amount of stability &amp;amp; correctness consider copy/paste to create 2 versions of the same class that can move forward independently in the direction that is best for each side of the API.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In a large, complex system, it’s important to minimize relationships between components. With tight coupling, changes in one part of the system cascade to others. It can become difficult to make a change anywhere without impacting a significant portion of the rest of the system. Even minor changes are risky because it’s difficult to estimate how they will affect other parts of the system.&lt;/p&gt;

&lt;p&gt;In addition, tight coupling (or any coupling) restricts changes that can be made. Or it requires significantly more effort than should be necessary as all of the coupled classes need to be modified as well. And, the higher the coupling in the system, the more the changes cascade causing – requiring changes, testing, and debugging throughout the system.&lt;/p&gt;

&lt;p&gt;By nature, an object-oriented system has significant amounts of coupling. Using module boundaries and data mapping can localize coupling complexity and significantly improve the stability of a large system undergoing changes.&lt;/p&gt;

&lt;p&gt;If you have any corrections, comments, or suggestions, let me know on &lt;a href="https://twitter.com/DevReliefFred"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>objectoriented</category>
      <category>coupling</category>
      <category>softwaredevelopment</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
