AEM - Get property of parent component dilalog inside child component's dialog - javascript

I'm very new to AEM and pure front end (html,css,js) so apologies for asking such a basic question. I have made 2 components parent and child. Each one of these components has a name property which I set in the dialog. I need to to be able to access the parents name property in the child component my page looks like this.
Parent Component
--parentParsys
----Child Component
------childParsys
I just need to display the parents name in the childs dialog but I can't seem to find the parents name.
Any guidance would be much appreciated. Ideally I'd like to do this in JS but I'm comfortable picking my way around a JSP.
Thanks

Attach a listener to your child component's dialog so that it will run your JavaScript when the dialog loads. That JavaScript will determine the path of the parent component based on the path of the child component. Using Apache Sling's RESTful nature, you will simply make a GET request to parent component's Resource, which will return you the properties of that resource. You can then update the current open dialog with the value that you want. All the information you need is provided in the CQ Widgets API documentation.
Attach the listener to your child dialog pointing to an external JavaScript file:
<instructions
jcr:primaryType="cq:Widget"
fieldLabel="Instructions"
xtype="displayfield">
<listeners
jcr:primaryType="nt:unstructured"
loadcontent="function(field, record, path){ mynamespace.updateWithParentName(field, record, path) }"
/>
</instructions>
Add the custom JavaScript to a ClientLib available in authoring mode. That could be cq.authoring.editor, see Using Client-Side Libraries. This will run every time the dialog opens. This example makes a synchronous GET request and updates a displayfield. You will update it depending on what you want to with the data, how you want to find the parent component, and you'll also want to add null checks.
var mynamespace = {};
mynamespace.updateWithParentName = function(field, record, path) {
var parentPath = getParentPath(path);
var parentComponent = CQ.shared.HTTP.eval(CQ.shared.HTTP.noCaching(parentPath + '.json'));
var parentName = parentComponent.name; // assuming the property name is "name"
field.setValue('Parent component name is: ' + parentName);
/* Go up two levels accounting for the current component and the parsys */
function getParentPath(path) {
var parts = path.split('/');
var parentPath = parts.slice(0, parts.length - 2).join('/');
return parentPath;
}
}

Related

Why isn't my svelte {#each} block reactive?

So basically I am playing with Svelte trying to spin up a quick app, details of the app aren't important but basically it hosts a bunch of embedded sites. see example here & for replicability:
https://svelte.dev/repl/6f3484554ef8489b9a5960487a0a1f95?version=3.47.0
My problem is that when I add a new url & title to the sites list, the {#each} block that creates the embedded views doesn't update to reflect the new state of the list, even though the list is clearly updating in the console output. Is it something to do with scope or is it a Svelte issue of not triggering reactivity on prop reassignments from components?
Update: some sites don't allow embedding so use https://wikipedia.org as a safe one for testing.
if you replace a hard-coded url in the sites list with wiki address it should work fine. i basically want a new window to pop up as the {#each} block creates a new SiteView component
There are several things wrong with your code, the first being that you do not propagate the changes made to the sites array back to the main application, you should use bind: to keep the two arrays in sync.
<InputBar bind:sites {site} />
The second is that you are modifying an object when adding a new site and then adding that object to the array, this will always be the same object so if you change it the previously added sites will also change. You can solve this by spreading the new object into the array instead:
function add() { sites = sites.concat({...site}); console.log(sites)}
// or alternatively
function add() { sites = [...sites, {...site}]; console.log(sites); }
That said, the application is not very "Svelte" like as it mixes responsibilities and exposes data to components that don't need that data. For example, why would the input bar need to know about the current sites ? It would be a lot better to have the input bar be just that, an input bar. When the user clicks 'add' it raises an event that says 'something has been added' and resets the fields. Then the parent is responsible to add it to the array. This will make for a more flexible solution. If you do that you will see there is also no reason to have a 'site' variable on the top level (or even have that object at all, you can just have two fields)
<script>
import { createEventDispatcher } from 'svelte'
let url = ''
let title = ''
const dispatch = createEventDispatcher()
function add() {
dispatch('add', { url, title })
url = ''
title = ''
}
</script>
<div class="rounded">
<p>Enter a site to stream:</p>
<input type="text" placeholder="www.example.com" bind:value={url}>
<br>
<input type="text" placeholder="example" bind:value={title}>
<button on:click={add}>add</button>
</div>
<InputBar on:add={(ev) => sites = [...sites, ev.detail]} />
On a final note, to add things to the head of the html use <svelte:head> instead.
If you want to change a value from another component you need to bind the property, otherwise the relationship is one-way only (from parent component to child).
<InputBar bind:sites {site}/>

Are dynamic arguments in Vue.js templates possible?

Here is my setup, and I know its not ideal (I inherited this project from a couple of developers that are no longer working on this web app). I'm trying to marry Vue.js and JQuery so that I can simply re-use JQuery based components (instead of rewriting them) on my Vue.js page (the two previous developers having design differences).
What I need is this: I am filling a table with components post page Mount. Everything about this table populates correctly except I have buttons in some of the cells in which I would like to use Vue.js to operate those buttons' functionality. I can add a v-on:click= attribute just fine but since these attributes are added to the buttons post Mount their Vue attributes are not being listened to. On the client side (Edge/Chrome in this case) I can see the vue attributes in the developers console when inspecting the element. I looked up examples such as this article:
How to make Vue js directive working in an appended html element regarding how to correctly re-render components after they are appended post Mount. Since these objects were created and appended to the web page after mounting they are not Reactive.
The article suggests creating a component that can then be mounted manually, using a template to create the component. I've tried this and found that using Vue.extend to create a new subclass/component now disallows me to access methods in my original app. Let me place some code and then I'll recap:
What I see on the client side console:
<button type="button" class="results_table_col_1_button" v-on:click="test()">200-715-122-306-ATP</button>
How the data table is being updated via JQUERY
display_search_results: function (data) {
data = JSON.parse(data);
let num = data.row_order.length;
if (num > 0) {
displayDataTable(data);
$("#tableContainer").show();
$("#test_count").text(num + " test" + (num === 1 ? '' : 's') + " found");
$('#dataDisplayTable tr').not(':first').each(function (i, el) {
let save_id = $(el).find('td.results_table_col_1').text()
$(el).find('button.results_table_col_1_button').attr("v-on:click", 'test()')
What I tried via the article
//How the new component is being created:
var testComp = new ATP_Button().$mount()
document.getElementsByClassName("results_table_col_1")[i].appendChild(testComp.$el)
//with the constructor:
var ATP_Button = Vue.extend({
template: '<button type="button" v-on:click="test()">Is this working</button>',
})
This creates the button but test is then undefined as it is not initialized or referenced to in the subclass object ATP_Button. How do I pass my 'main' Vue app's test() function to this component so that when the button $emits its event the parent's test() function is called?
I also tried adding props to the constructor so that I could maybe pass test() into the component upon creation like this:
var ATP_Button = Vue.extend({
props: ['test'],
template: '<button type="button" v-on:click="test_child()">Is this working</button>',
methods: {
test_child: function () {
this.test
alert(this.test)
}
}
})
And created the component as such:
var testComp = new ATP_Button(this.test_func).$mount()
Now however this.test inside the component is undefined as it would seem that passing the function through is not working properly (probably due to developer error aka me).
RECAP
A JQUERY table object is being populated via a backend process with HTML elements to be displayed on the front end view of the webpage
One element that is added is a button that needs to be able to call a Vue.js method from the main app
The elements are added to the table after the page has been mounted thus they are not reactive
Trying to manually mount the component creates a subclass object that does not have access to the main app's methods
Trying to pass the main app's method test() to the component during construction failed and left the test variable undefined
Eventually I need the buttons to call a method from the main app with dynamic variables inside as arguments
I appreciate anyone who is able to offer any assistance.

Material Table not reflecting changes on datasource

This is my first question in Stack Overflow. I'll try to be specific but I don't know how to keep this short, so this is going to be a long post. Sorry about that. I promise I searched and tried a lot of stuff before asking, but I'm kind of lost now.
I'm developing a simple app in Angular 6 to keep track of software requisites and the tests associated to those requisites.
I have a component, called RequisiteList, whose HTML part consists in a mat-table with an Array of my own Requisite model class as [dataSource]. This array is received as an #Input parameter, and it also has an #Output parameter which is an EventEmitter that notifies and passes to the parent component every time a Requisite on the list is clicked.
I make use of RequisiteList inside of ReqListMain, which is a component consisting on the list and a hierarchical tree for filtering. This component is working fine, showing, and filtering requisites as intended. This component also captures the #Output event of the list and passes it as an #Output to its parent.
Finally (for what it's related to this question), I have a TestView component that has both an instance of RequisiteList to show the requisites currently associated to current test, and an instance of ReqListMain to add new requisites to current test (like a "browser"). This TestView has an instance of the model class Pectest corresponding to the test that is being currently visualized, which has an array of Requisite.
The idea in this last component was that whenever a requisite of the "browser" list was clicked, it was added to the current test's list. In order to do that, in the callback method associated to the #Output event of the browser list, I tried to add the Requisite received as a parameter:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
console.log('Current test: ');
console.log(this.currentTest);
}
In the HTML part of TestView, I inserted the RequisiteList component like this:
<app-requisitelist [requisites]="currentTest.requisites" ngModel name="reqlistview"></app-requisitelist>
(The ngModel property is part of the things I've been trying, I'm not sure it's necessary).
The result is:
The clicked requisite is not shown in the list.
In the console output I can see the content of currentTest object, and I verify that clicked requisites are in fact added to the requisites array of that object, so the event fires and the object is passed upwards by the children components.
I'm not sure if my problem is that data binding is made by value (I don't think so, as I bind an Array, which is an object AFAIK), or the table is not detecting data changes (I've tried to force data change detection with ChangeDetector), or anything else.
You pass a array to the app-requisitelist component. This component waits this array changes to update the content. When you do this.currentTest.requisites.push(requisite), the array this.currentTest.requisites doesn't change, I mean, if you do
const tmp = this.currentTest.requisites;
this.currentTest.requisites.push(requisite)
if (tmp === this.currentTest.requisites) {
console.log('The arrays are the same');
}
You will get the log printed. So, I suggest do something like that:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
this.currentTest.requisites = this.currentTest.requisites.map(item => item);
console.log('Current test: ');
console.log(this.currentTest);
}
The inserted line forces this.currentTest.requisites to be a new array with the same content.

How to avoid a full refresh of a widget on data change?

In icCube Reporting V6, I created a template widget based on iccube's example at https://www.iccube.com/your-first-html-widget/. So doing, i've put all the JS code in the After render event.
I'd like to have some variables defined and defaulted at widget creation time which could be changed during the use of the whole report and be used as parameters in this widget.
The problem is that at each data change, the widget is fully refreshed and reconstructed, Resetting my variables to default as well...
Is it missing a init widget event in which we could initialize the widget and the variables ? then the on Data Received and After render events would just be aimed to manage data manipulations and rendering ? (with regard to these variables)
Or… am I just doing it a wrong way ?
After Render Event is called everytime we get new data or one of the property of the widget has change. The latest is rare but possible if some of the properties have been defined using an event.
What we need is to add a status on this method to check if the widget has been already rendered. This can be done in two way, checking with jquery for the dom has changed or adding a javascript object.
The first one is easy with jquery and should be use if there is no need to cache javascript object. For the second one we've two solutions :
1) We can use jquery to bind a javascript object to a html element ( data ) :
var state = $node.data("widgetState");
if ( state ) {
// it's not the first time
state.times = state.times+1;
$node.html("It's the " + state.times + " time you clicked " );
}
else
{
// it's the first time
$node.html("It's the first time you clicked, don't be shy" );
state = { times : 1 } ;
$node.data("widgetState", state);
}
2) Since icCube 6.0.4 you can also use the context to get a local state object, context.widgetState()
You can check a working example here :

How to check if a backbone view is rendered?

I am trying to append a view to an item in Backbone with a following code:
var viewContainer = this.$el.find('.view-container'),
pageWrap = this.$el.nextAll();
FIX
if (viewContainer.empty()) {
this.myView= new ProductsView();
viewContainer.append(application.myView.render().$el),
console.log(myView);
}
I am appending this view to the viewContainer with a toggle function, however, every time I click on the button, myView is appended again and again to the viewContainer instead of of only once. How do I check if the view is already rendered inside it before appending it? Is there a !this.rendered() equivalent I can use?
I found this thread but it is not helping me in this instance.
UPDATE - FROM console.log(viewContainer)
[div.view-container.product-container.active, div#subjects_menu.view-container.product-container.hidden.active, prevObject: p.fn.p.init[1], context: undefined, selector: ".view-container"]
From the looks of it, you want to make sure ProductsView is not created if it already exists.
Simplest way to do this would be:
if(!this.myView) {
this.myView= new ProductsView();
viewContainer.append(application.myView.render().$el),
}
It is better to work with application state than querying DOM. When you remove product view, simply do this.myView = null afterwards.
The only time you'd want to query DOM to know if a view is rendered is probably when you have to integrate an isolated external application over which you have no control that doesn't trigger any event/provide callbacks etc while rendering.

Categories

Resources