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.
Related
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}/>
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;
}
}
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.
First of all I know how to set a LoadingMask for a component but have a problem with the uncoupling of the system I am making so I am just looking for a hint/idea.
I am using a MVVC architecture and in the View I have a Container with several Components in it, one of which is a Grid.Panel. The grid is bound to a store and has an event that when fired calls a method of the store. The following code happens in the ViewController:
functionForEvent() {
var store = getStoreForThisGrid();
store.update(Ext.getBody());
}
What happens now is the update() method makes a request to a server, that updates the store itself and the view component, and I need the loading mask during that time. How I handle the situation right now is I pass Ext.getBody() (or a DOM Element representation of a specific component) to the method and it deals with that reference. This function part of the store that is attached to the Grid and resides in the Store:
update : function (el) {
el.mask();
makeRequest();
el.unmask();
}
What I am looking for is another way (Pattern maybe if such exists for JavaScript) to access the View component from the Store instead of passing it around because that does not seem like a good practice and couples the system.
Since I come from a Java background I would have used the Observer pattern but cannot find how to apply this in JS.
Problem: Meteor JS app with 2 distinct templates that need to share some data.
They are dependent on one another, since I aim to extract text (Step 1) from one, and then create dynamic buttons (Step 2) in another template. The content of the buttons is dependent on the table.
buttons.html
<template name="buttons">
{{#each dynamicButtons }}
<button id="{{ name }}">{{ name }}</button>
{{/each}}
</template>
My goal is for the name property to come from the content of reactiveTable.html (see above, or their Github page, package meteor add aslagle:reactive-table.
These need to be dynamically linked since table re-renders constantly w/ different group of products, which are linked up through Template.reactiveTable and a specific data context (Pub/Sub pattern).
IF the table is (re)rendered, then parse it's content and extract text. Once the table is parsed, dynamically inject newly created buttons into the UI. Note UI.insert takes two arguments, the Object to insert, and then location (DOM node to render it in).
Template.reactiveTable.rendered = function () {
UI.insert( UI.render( Template.buttons ) , $('.reactive-table-filter').get(0) )
};
(Insert new buttons every time a reactiveTable is rendered.)
This code works, but is flawed since I cannot grab the newly rendered content from reactiveTable. As shown in this related question, using ReactiveDict package:
Template.buttons.helpers({
dynamicButtons: function() {
var words = UI._templateInstance().state.get('words');
return _.map(words, function(word) {
return {name: word};
});
}
});
Template.buttons.rendered = function() {
// won't work w/ $('.reactiveTable) since table not rendered yet, BUT
// using $('h1') grabs content and successfully rendered dynamicButtons!
var words = $('h1').map(function() {
return $(this).text();
});
this.state.set('words', _.uniq(words));
};
Template.buttons.created = function() {
this.state = new ReactiveDict;
};
How can I change my selector to extract content from Template.reactiveTable every time is re-renders to create buttons dynamically? Thanks.
You’re using a lot of undocumented functions in there, and UI.insert and UI.render which are bad practice. The just-released Meteor 0.9.1 eliminates them, in fact.
Create your dynamic buttons the Meteoric way: by making them dependent on a reactive resource. For example, a Session variable. (You could also use a client-side-only collection if you want.)
Template.reactiveTable.rendered = function () {
// Get words from wherever that data comes from
Session.set('buttons', words);
};
Template.buttons.helpers({
dynamicButtons: function() {
if (Session.equals('buttons', null))
return [];
else
return _.map(Session.get('buttons'), function(word) {
return {name: word};
});
}
});
Every time reactiveTable is rendered or rerendered, the buttons Session variable will update. And because your dynamic buttons are depending on it, and since Session variables are a reactive resource, the buttons will rerender automatically to reflect the changes.