Comment from OP:
Neither of these approaches work across namespaces because the window object for each namespace is a different instance, as part of the Locker Service.
See the answer from the OP that explains how the only commonality between the two window objects is the addition of event listeners and the sending of DOM events; it is currently possible (including in the Summer '20 release) to tunnel DOM events across the namespace boundary.
added:
Problem:
LWC is importing different resources (apex/static/modules/wired etc) in different ways. Importing modules is identical to standard ES6 imports. However when you import static resource, it is actually wrapped inside a named function and is executed when we invoke it in loadScript
.
You will be able to upload it as static resource but will not compile at run time (because of export statement) during the import because of which no named function will be created (took a hell lot of debugging) and loadScript will directly go to catch statement. For easy understanding try to save below function in any LWC component:
staticResourceLoad() {
const mydouble = (num) => {
return num * 2;
};
export { mydouble };
}
This will not even compile (Parsing error: 'import' and 'export' may only appear at the top level). So, when you want to load a module as a static resource, you need to do below changes in JS file - most importantly you need to define a variable on window so that you have access to that variable after the file is loaded.
Solution:
You cannot use the module as is in static resource. You need to implement it using self-invoking function and define global variable (like pubsub) and assign all the functions needed (like samePageRef) and return it so that it can be assigned to different var if needed.
Below is the pubsub which can be used for static resource:
(function() {
window.pubsub = {};
/**
* A basic pub-sub mechanism for sibling component communication
*
* TODO - adopt standard flexipage sibling communication mechanism when it's available.
*/
let events = {};
/**
* Confirm that two page references have the same attributes
* @param {object} pageRef1 - The first page reference
* @param {object} pageRef2 - The second page reference
*/
pubsub.samePageRef = (pageRef1, pageRef2) => {
const obj1 = pageRef1.attributes;
const obj2 = pageRef2.attributes;
return Object.keys(obj1).concat(Object.keys(obj2)).every((key) => {
return obj1[key] === obj2[key];
});
};
/**
* Registers a callback for an event
* @param {string} eventName - Name of the event to listen for.
* @param {function} callback - Function to invoke when said event is fired.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
pubsub.registerListener = (eventName, callback, thisArg) => {
// Checking that the listener has a pageRef property. We rely on that property for filtering purpose in fireEvent()
if (!thisArg.pageRef) {
throw new Error(
'pubsub listeners need a "@wire(CurrentPageReference) pageRef" property'
);
}
if (!events[eventName]) {
events[eventName] = [];
}
const duplicate = events[eventName].find((listener) => {
return listener.callback === callback && listener.thisArg === thisArg;
});
if (!duplicate) {
events[eventName].push({ callback, thisArg });
}
};
/**
* Unregisters a callback for an event
* @param {string} eventName - Name of the event to unregister from.
* @param {function} callback - Function to unregister.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
pubsub.unregisterListener = (eventName, callback, thisArg) => {
if (events[eventName]) {
events[eventName] = events[eventName].filter(
(listener) => listener.callback !== callback || listener.thisArg !== thisArg
);
}
};
/**
* Unregisters all event listeners bound to an object.
* @param {object} thisArg - All the callbacks bound to this object will be removed.
*/
pubsub.unregisterAllListeners = (thisArg) => {
Object.keys(events).forEach((eventName) => {
events[eventName] = events[eventName].filter(
(listener) => listener.thisArg !== thisArg
);
});
};
/**
* Fires an event to listeners.
* @param {object} pageRef - Reference of the page that represents the event scope.
* @param {string} eventName - Name of the event to fire.
* @param {*} payload - Payload of the event to fire.
*/
pubsub.fireEvent = (pageRef, eventName, payload) => {
if (events[eventName]) {
const listeners = events[eventName];
listeners.forEach((listener) => {
if (samePageRef(pageRef, listener.thisArg.pageRef)) {
try {
listener.callback.call(listener.thisArg, payload);
} catch (error) {
// fail silently
}
}
});
}
};
return pubsub;
})();
For importing:
import pubsubMod from '@salesforce/resourceUrl/pubsub';
For init:
loadScript(this, pubsubMod)
.then(() => {
this._pubsubInit = true;
// Tentatively register, in case the page reference is already resolved
pubsub.registerListener('contactSelected', this.handleContactSelected, this);
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error loading pubsub',
message: error.message,
variant: 'error'
})
);
});
Note that pubsubMod refers to resource and pubsub refers to global var window.pubsub
sample code for handling:
handleContactSelected(val) {
console.log('val => ', val);
}
OPTION 2:
As you need a temporary solution until UI Message service is available, I think you can use javascript variable change handlers at window scope as a work around. I created playground link for understanding. As it will be at window
scope, I think it will work for cross-namespace (I did not test it though)
Below is the sample code:
mytargetcomp.js:
connectedCallback() {
window.myvar = {
aInternal: 10,
aListener: function(val) {},
set a(val) {
this.aInternal = val;
this.aListener(val);
},
get a() {
return this.aInternal;
},
registerListener: function(listener) {
this.aListener = listener;
}
};
window.myvar.registerListener(function(val) {
console.log('Someone changed the value of myvar to ', val);
});
}
corens__mysourcecomp.js:
refreshData() {
window.myvar.a = 'some data ' + new Date().getTime();
}
This is when you want to pass data from corens__mytargetcomp
to mycomp
.
You can implement and invoke refreshData
from any component and whichever component implements change handler should be able to handle new data.
Best Answer
Unfortunately I concluded that, since Salesforce gives you a Proxy object back instead of the raw DOM element, it is currently not possible to do any sort of on-the-fly masking of an input value. I settled for a LWC that grabs the value after it has been typed, formats it appropriately, puts it back in the field, and runs validation. When the user focuses on the input field it removes the mask and lets them edit the "raw" value again:
HTML
JavaScript