AEM 6.2 get component property from parent page - javascript

I have a footer element which needs to be shared. My plan is to set up the footer on the parent/home page but allow child pages to overwrite those properties.
I first look on the current component for the property (pretty standard), after that I get the path to the parent page to look for the property on the component with the same name.
function getProperty(property, currentPage) {
var val = null,
page = currentPage,
rootPage = page.getAbsoluteParent(2);
var curNode = currentNode.getPath(),
nodeStrIdx = curNode.indexOf("jcr:content"),
nodeStr = curNode.substr(nodeStrIdx + 12); // Remove 'jcr:content/' too
while(val == null) {
// If we've gone higher than the home page, return
if(page.getDepth() < 3) {
break;
}
// Get the same node on this page
var resource = page.getContentResource(nodeStr);
if(resource != null) {
var node = resource.adaptTo(Node.class); // *** This is null ***
// val = node.get(property);
}
// Get the parent page
page = page.getParent();
}
return val;
}
I've seen that you can change the type of the content resource to a node which should allow me to get the same property but the resource.adaptTo(Node.class) is returning null.
In case it's not clear, resource is an absolute path to the node I want to pull the property from e.g. /content/jdf/en/resources/challenge-cards/jcr:content/footer/follow-us

Assuming you are using Javascript HTL Use API, you need to use fully qualified name for Java class, like this:
var node = resource.adaptTo(Packages.javax.jcr.Node);
Then you can retrieve your value in this way:
if (node.hasProperty(property)) {
val = node.getProperty(property).getString();
}
You need to use hasProperty method per Node API as getProperty throws PathNotFoundException when a property is missing. You also need to be careful with granite.resource object from examples - it is not the same Resource object and it does not have adaptTo method. To get to the original resource of the component you need to take nativeResource property:
var node = granite.resource.nativeResource.adaptTo(Packages.javax.jcr.Node);
However, there also should be a faster way to get property from the resource in JS:
val = resource.properties[property];
As this is a quite common case to develop components properties inheritance, you can also consider some ready-to-use solutions like HierarchyNodeInheritanceValueMap API or Inheritance Paragraph System (iparsys) in your implementation design.
As this JS is server-side compiled with Mozilla Rhino, all these objects and methods used here are Java objects and methods so that you should be also able to use HierarchyNodeInheritanceValueMap in this way:
//importClass(Packages.com.day.cq.commons.inherit.HierarchyNodeInheritanceValueMap);
//this import might be needed but not necessarily
var props = new HierarchyNodeInheritanceValueMap(granite.resource.nativeResource);
val = props.getInherited(property, Packages.java.lang.String);
It will then return to val the property value for the current resource, or, if empty, the property value of the resource at the same position on the parent page, or if empty etc. These two lines should do all the magic then.

Related

Is it safe to override window.sessionStorage.getItem and setItem?

In my SPFx web part I am using an npm package that defines React.js controls that I need. The library is https://github.com/pnp/sp-dev-fx-controls-react, but my question is not strictly related to this library.
For specific reasons I need to use an older version of this library.
One of the controls I use reads data from the server and caches it in session storage (it's the TaxonomyPicker). Now in my use case, this data can be more than 7MB in size and the code fails where it tries to call setItem(key, value) with this long string. Unfortunately this error leads to the control not rendering.
Since I didn't want to change the (old version of the) library I am using, I decided to override the window.sessionStorage.getItem and setItem methods by functions that compress the string before it is saved and decompress it after it has been read with getItem. I do this only for large strings. Here's my implementation (in TypeScript):
fixSetItemFunc = (): void => {
const lzPrefix = 'lz:';
const threshold = 5000;
if (!this.oldSetItemFunc) {
this.oldSetItemFunc = window.sessionStorage.setItem.bind(window.sessionStorage);
const setItem = (key: string, value: string) => {
let compressedValue: string;
if (value && value.length > threshold) {
compressedValue = lzPrefix + LZ.compress(value);
}
else {
compressedValue = value;
}
this.oldSetItemFunc(key, compressedValue);
};
window.sessionStorage.setItem = setItem;
this.oldGetItemFunc = window.sessionStorage.getItem.bind(window.sessionStorage);
const getItem = (key: string) => {
const value = this.oldGetItemFunc(key);
let decompressedValue: string;
if (value && value.length && value.substring(0, lzPrefix.length) === lzPrefix) {
decompressedValue = LZ.decompress(value.substring(lzPrefix.length));
}
else {
decompressedValue = value;
}
return decompressedValue;
};
window.sessionStorage.getItem = getItem;
}
}
Now the interesting thing is that this workaround seems to work. But since session storage can live quite a while and users can navigate away from the page where I add the override, I am wondering if this is in anyway a reliable solution. Because all other code that is run on other SharePoint pages will use my overridden methods after I called fixSetItemFunc() once in my web part.
So I would be interested in your thoughts on this.
As you said, this override will apply and could potentially affect all the code on the page, not just your code. Why don't you instead break the long string into fragments and store those fragments as separate keys into the sessionStorage following some smart naming convention for the keys? That way you could easily isolate the logic for handling of large strings to your code only, without having to override a global method.
Btw, keep in mind that the sessionStorage is an in-memory storage private to the current browser window/tab, so every window/tab will have to maintain its own copy of the data.

Making a custom group of defined chaining methods in js

The question is related to general js programming, but I'll use nightwatch.js as an example to elaborate my query.
NightWatch JS provides various chaining methods for its browser components, like: -
browser
.setValue('input[name='email']','example#mail.com')
.setValue('input[name='password']', '123456')
.click('#submitButton')
But if I'm writing method to select an option from dropdown, it requires multiple steps, and if there are multiple dropdowns in a form, it gets really confusing, like: -
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
Is it possible to create a custom chaining method to group these already defined methods? For example something like:
/* custom method */
const dropdownSelector = (id, value) {
return this
.click(`${id}`).
.waitForElementVisible(`${value}`)
.click(`${value}`)
}
/* So it can be used as a chaining method */
browser
.dropdownSelector('country', 'india')
.dropdownSelector('state', 'delhi')
Or is there any other way I can solve my problem of increasing reusability and readability of my code?
I'm somewhat new to JS so couldn't tell you an ideal code solution, would have to admit I don't know what a proxy is in this context. But in the world of Nightwatch and test-automation i'd normally wrap multiple steps I plan on reusing into a page object. Create a new file in a pageObject folder and fill it with the method you want to reuse
So your test...
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
becomes a page object method in another file called 'myObject' like...
selectLocation(browser, country, state, city) {
browser
.click(`#country`) <== assume this never changes?
.waitForElementVisible(country)
.click(country)
.click(state)
.waitForElementVisible(city)
.click(city);
}
and then each of your tests inherit the method and define those values themselves, however you chose to manage that...
const myObject = require ('<path to the new pageObject file>')
module.exports = {
'someTest': function (browser) {
const country = 'something'
const state = 'something'
const city = 'something'
myObject.selectLocation(browser);
You can also set your country / state / city as variables in a globals file and set them as same for everything but I don't know how granular you want to be.
Hope that made some sense :)
This is a great place to use a Proxy. Given some class:
function Apple ()
{
this.eat = function ()
{
console.log("I was eaten!");
return this;
}
this.nomnom = function ()
{
console.log("Nom nom!");
return this;
}
}
And a set of "extension methods":
const appleExtensions =
{
eatAndNomnom ()
{
this.eat().nomnom();
return this;
}
}
We can create function which returns a Proxy to select which properties are retrieved from the extension object and which are retrieved from the originating object:
function makeExtendedTarget(target, extensions)
{
return new Proxy(target,
{
get (obj, prop)
{
if (prop in extensions)
{
return extensions[prop];
}
return obj[prop];
}
});
}
And we can use it like so:
let apple = makeExtendedTarget(new Apple(), appleExtensions);
apple
.eatAndNomnom()
.eat();
// => "I was eaten!"
// "Nom nom!"
// "I was eaten!"
Of course, this requires you to call makeExtendedTarget whenever you want to create a new Apple. However, I would consider this a plus, as it makes it abundantly clear you are created an extended object, and to expect to be able to call methods not normally available on the class API.
Of course, whether or not you should be doing this is an entirely different discussion!

monkey-patching react's render method in devtools extension

I'm trying to create a monkey patch to React's render method from a devtool extension because I'm trying to recreate something similar to react_devtool's api for a feature in my extension.
I'm spoofing the inspected window's virtual DOM with these 2 lines of code
var reactRoot = document.querySelector("[data-reactroot]")
var dom = reactRoot[Object.getOwnPropertyNames(reactRoot)[0]]
I've also heard of accessing this in the same way by accessing the window's __REACT_DEVTOOLS_GLOBAL_HOOK__. I need to be able to access a new set of data from React's internalInstance anytime there is an update to the page (setState/rerender).
This is my attempt at monkey patching React's render method. This code is running from a separate file injected through my content-scripts.jswhich is able to access the dom of a React application in the inspected Window.
const reactInstances = window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers
const instance = reactInstances[Object.keys(reactInstances)[0]]
console.log('current windows React INSTANCE: ', instance)
var reactRender = instance.Mount.render
console.log('reacts render method: ', reactRender)
reactRender = (function (original) {
return function (nextElement, container, callback) {
var result = original.apply(this, arguments)
console.log(original, result)
return result
}
})(reactRender)
Not sure if this is the correct way to monkey patch a method but I'm also wondering if this is the correct approach to what I'm trying to accomplish.

Bacon.js Property new value and old value

Working with a POC using Bacon.js and run into a little bit of an issue with Property values.
I am able to retrieve all new property values in the onValue callback however I would like to know what the old property value was before this new value has been set. So far I have not found any easy or elegant solution to achieve this in Bacon out of the box...am I missing something.
Even Object.observe() has a way to get to the old value of the property so surprised I cannot find equivalent behaviour in Bacon.
Would anyone have any suggestions how to handle this? Obviously I do not want to persist the latest property value anywhere in the client code strcily for the sake of being able to do the comparisons between old and new...
You could use slidingwindow to create a new observable with the 2 latest values:
var myProperty = Bacon.sequentially(10, [1,2,3,4,5]) // replace with real property
var slidingWindow = myProperty.startWith(null).slidingWindow(2,2)
slidingWindow.onValues(function(oldValue, newValue) {
// do something with the values
})
Due to lack of the functionality in the native Bacon.js I implemented a custom solution with a wrapper object that holds on to the latest value after it had been forwarded to the subscriber. So instead of tapping into the lens directly client code works with the obsever which also has additional benefits of restricting write access to the model through the lens from client code which is what I needed as well.
function Observer (lens) {
this.lastValue = undefined;
this.lens = lens;
};
Observer.prototype = {
onValue(onValueCallback){
this.subscription = this.lens.onValue(function(newValue) {
onValueCallback(this.lastValue, newValue);
this.lastValue = newValue;
});
},
};
var model = new Bacon.Model({ type : "A"});
var observer = new Observer(model.lens());
observer.onValue(function(oldValue,newValue) { ... });

Dust.js overwrites Knockout observables on render

I am using Dust.js and Knockout.js together on a project, using a module called Duster-KO to integrate the two. The problem happens when I am trying to render a dust template client-side: when I pass an observable, or any object containing an observable, to dust.render() in the Context parameter, Dust is actually setting the KO observable to be a "Chunk" object. I believe this is because Knockout observables are functions, and so Dust thinks that the function I am passing it is a callback instead of an observable, which it is then executing and somehow setting the observable that way.
Is there any way to avoid this issue, or otherwise prevent Dust from touching the observables?
Here is an example of a situation I have run into:
var guest = exports.guest = function(opts) {
this.first = ko.observable(opts.first||"")
this.last = ko.observable(opts.last||"")
// ... more model code here
}
var table = exports.table = function(opts) {
// This is an observable array of guest objects
this.guests = ko.observableArray(opts.guests||[])
this.template = "tableTemplate"
this.target = opts.target // This is whatever DOM element we are injecting the template into
// ... more model code here
var self = this
this.draw = function() {
// Before we render the Dust template, the guest's first and last name are as they should be
// this.ctx is a Context object inherited from another parent object, which has the current object pushed onto it
var rendered = dust.render(self.template, this.ctx)
// At this point in the code, the guest's first and last name have been set to Chunk objects, rather than their actual first and last names
self.target.appendChild(rendered)
}
}
In the above example, before I render the dust template each guest's first and last names are intact and as they should be. However, afterwards they are changed to Chunk objects.
And before someone suggests it, removing Dust and using only Knockout is not an option right now unfortunately.
Did u apply the hack mentioned in the Duster-Ko Readme file???
Why the Dust hack :(
Unpleasant business, that.
Dust expects any functional tags to accept a set of parameters (chunk,
context). We could build a Dust-friendly wrapper for every KO
Observer, and build Dust contexts out of these, but that seems like an
awful lot of unnecessary object creation.
Instead, we just hack Dust to not evaluate Observers like it normally
would, and handle the aftermath with a more stock standard
helper-filter.
The Source
These changes are done in whatever dust*js you're using.
Prime hackery:
Chunk.prototype.reference = function(elem, context, auto, filters) {
- if (typeof elem === "function") {
+ if (typeof elem === "function" && elem.name != "observable") {
elem = elem(this, context, null, {auto: auto, filters: filters});`
Oh, also, we're manually invoking some Dust templates & cough eval'ing
that. To manually invoke the template, we need to pass in a Dust Chunk
object, which normally we wouldn't be exposed to, so:
+dust.chunk= Chunk Tis all! Checkout lib/dust-patch.js for a patch
against unspecified Dust sources (for now, dust-core-0.3.0.js is the
intended target).

Categories

Resources