How do I access page fragment's custom properties within an event handler of one of its widget? For example, I'm trying to access them in an OnLoad event of the page fragment itself like this:
function SumBoxOnDataLoad (widget) {
widget.descendants.TextBox.value = Currencies [widget.properties.currency]+widget.properties.value;
}
...and widget.properties is undefined. According to the API, even if I use widget.root, which points to the same page fragment, there's nothing there resembling properties to work with.
It depends on what you are trying to do. Basically, there are two major use cases:
Workaround lack of global bindable variables in App Maker. Page Fragment itself (not an instance of Page Fragment on particular page) has its own properties, that can be used globally across the app. This hack/feature is used in Starter App template to track menu state on app level.
...
var x = app.pageFragments.MyPageFragment.properties.MyCustomProperty;
...
Use properties of particular Page Fragment instance:
// access from current page:
var x = app.currentPage.descendants.MyPageFragmentInstance.properties.MyCustomProperty;
// ...or
var x = app.pages.MyPage.descendants.MyPageFragmentInstance.properties.MyCustomProperty;
// access in page fragment instance's event (onAttach for example)
var x = widget.parent.properties.MyCustomProperty;
// access in page fragment instance descendant's event
var x = widget.root.parent.properties.MyCustomProperty;
Further reading:
Page Fragment Custom Properties
Related
Is there a way to detect which node is being rendered when the user selects a link within the pages detail app and only the iframe changes? If just the the iframe is changing I need a mechanism that can call the parent page and send the path of the node that is being rendered in the iframe. We run Magnolia EE 5.6.11.
My issue is that I have a ValueChangeListener in a ComboBox that I use as a versionSelector in a PageBar extension in the pages detail app.
// Create a selection component;
private ComboBox versionSelector = new ComboBox();
private Listener listener;
private boolean isSettingValue;
public VersionSelectorViewImpl(){
construct();
}
private void construct() {
versionSelector.setVisible(false);
versionSelector.setSizeUndefined();
versionSelector.setImmediate(true);
versionSelector.setNullSelectionAllowed(false);
versionSelector.setTextInputAllowed(false);
//setup listener for the selected item
versionSelector.addValueChangeListener(new Property.ValueChangeListener() {
#Override
public void valueChange(Property.ValueChangeEvent event) {
if (listener != null) {
listener.versionSelected((Object) event.getProperty().getValue());
}
}
});
}
The implementation is similar to the LanguageSelector or VariantSelector. When someone activates a hyperlink in the page template the iframe changes and the valueChange method retrieves the wrong value (ie. the event is from the previous page).
When someone activates a link, the PagesEditorSubApp#updateNodePath calls the updateLocationDependentComponents which calls the PageBar.onLocationUpdate. This calls in our case the VersionSelector#setCurrentVersion method. At this point I would need to reload the page detail subapp so that the listener is correctly set to the new page. I tried using the pageEditorPresenter.refresh() method in the setCurrentVersion method but it didn't do it.
In Magnolia 5.x you need to fire ContentChangeEvent to trigger the refresh (and hope that subapp you want to refresh listens to it, which in case of page detail subapp shouldn't be a problem).
Or, since you mention LanguageSelector, you can indeed try to call PageEditorPresenter directly as it does from info.magnolia.pages.app.editor.pagebar.languageselector.LanguageSelector#languageSelected, tho unless you need to modify something in the presenter (eg. locale in the example above), it seems rather unnecessary coupling of the code.
Once you are migrating your code to Magnolia 6.x and new UI framework, you have more convenient way of triggering notification of datasource change which all the views consuming data from given source listen to and facilitating refresh that way (as is shown eg in PasteComponentAction here.
Google Tag Manager (GTM) has a built in trigger called "Just Links". In my VueJS application, using Vue Router, GTM fires a "History Change" event before firing the "Just Links" trigger.
(the "History Change" event has nothing to do with page view events)
Because of this, the Page Path GTM data-layer variable, which is supposed to be the path that the event was triggered on, is the same value as the Click URL GTM data-layer variable, which is the href value in the <a/> tag.
For instance:
User is on /support
User clicks on link to /about
Vue Router update browsers history
History Change event fires and updates all the internal values of Google Tag Manager data layer (including location and page path)
"Just Links" event fires, Page Path and Click URL values are now both /about
I'm assuming GTM/Google Analytics have some type of built in deferment strategy in place to not interfere with other things running on the main Javascript thread. So Vue Router changes routes (hence triggering the history change) before GTM/Google Analytics fire the "Just Links" trigger event.
(Strangely enough, when you use the built in Click - All Elements in GTM, the events fire in the right order.)
Has anyone else encountered this issue and come up with any type of solution? There might be a solution with just using Click - All Elements but that by default doesn't traverse the DOM tree with <a/> and strip the href for the Click URL value, which means extracting the href value on nested elements within an <a/> doesn't work. However, Just Links does do this.
Thanks!
You can look at tag firing priority. Adjust the click tag to have higher priority than the tag that's dependent on the history change trigger.
I just ran into this problem due to a site update. Instead of switching all of my "Just Links" to "All Elements" and dealing with the many headaches, I created a new Variable to reference for the page path instead of the built-in "Page Path" Variable. This Variable is basically storing what "gtm.linkClick" now treats as the previous page path because of the History Change. I'm just at beginner-level JS, so I'm sure there is a better way to write the code, but it works.
The first step is to create a new Custom HTML Tag (name it whatever you want) set to fire on All Pages and History Changes. The Tag will first attempt to store the current page path into Session Storage, or fallback to a cookie if Session Storage is not accessible. I used the {{JS - setCookie}} Variable method that was created by Simo Ahava to set cookies. There is a 2 second delay before writing the page path to storage, which is plenty of time for the "Just Links" Trigger to fire and receive the "correct" value before it is overwritten on the next pageview or history change. I gave the sessionStorage and cookie the same name "gtm.truepage".
Custom HTML Tag
<script>
function truePage() {
try {
var page = document.location.pathname;
var storage = window.sessionStorage;
if(storage) {
setTimeout(function() {
storage.setItem("gtm.truepage", page);
},2000);
} else {
setTimeout(function() {
{{JS - setCookie}}("gtm.truepage",page,1800000,"/",window.location.hostname);
},2000);
}
} catch (e) {
}
}
truePage();
</script>
Custom HTML Tag Screenshot
1st Party Cookie Variable
The next step is to create a new 1st Party Cookie Variable named "gtm.truepage". This Variable will be used as a reference for the Custom Javascript Variable. Therefore, if you change the name, you will also need to change the name in the next part.
Cookie Variable Screenshot
Custom Javascript Variable
The final step is to create a Custom Javascript Variable (whatever name you want) that you will use for your "Clicks - Just Links" Trigger. The script will first attempt to retrieve the page path that was set by the Custom HTML Tag from Session Storage and then try the 1st Party Cookie Variable.
function() {
return window.sessionStorage.getItem('gtm.truepage') || {{gtm.truepage}};
}
Custom Javascript Variable Screenshot
You can add an after hook to your router to create a custom path variable like this:
router.afterEach((to) => {
window.vuePath = to
}
Then in Google Tag Manager create a new User-Defined Variable with the JavaScript Variable type called Vue Path. Set Global Variable Path to window.vuePath.
Now you can use Vue Path instead of Page Path in your Triggers to get the correct page path.
In my UI5 application, I have an i18n.properties file with keys and values:
#XMSG:
qty=Quantity
And I'm using this property value in a dialog box as title
onUpdateDialog: function() {
var that = this;
var dialog = new Dialog({
title: "{i18n>qty}",
// ...,
});
dialog.open();
},
But when I run my application, the dialog box title is not getting displayed:
When I use text values from i18n property file somewhere else it's getting displayed.
ManagedObjects, that are created by the application code imperatively outside of the framework-managed features (Such creating an instance of sap.m.Dialog in a Controller code without using the API loadFragment), have to be added to the model delegation chain manually in order to make use of the propagated models.
In order to do so, add the created instance to the parent's <dependents> aggregation. E.g.:
this.getView().addDependent(myDialog); // myDialog is now aware of the "i18n" model
From API Reference:
Special aggregation dependents is connected to the lifecycle management and databinding, but not rendered automatically and can be used for popups or other dependent controls or elements. This allows the definition of popup controls in declarative views and enables propagation of model and context information to them.
In order to use texts in the controller you need to fetch the text first, like so:
this.getOwnerComponent().getModel("i18n").getResourceBundle().getText("qty")
this will be the dialog inside the dialog, so declare a that before the dialog and change this to that..
var that = this;
that.getOwnerComponent().getModel("i18n").getResourceBundle().getText("qty")
Hope this solves your issue..
At the point of opening the Dialog, it doesn't know the i18n model. You need to provide the model to the dialog by calling dialog.setModel(this.getModel('i18n'), 'i18n') before opening the dialog.
I seem to be struggling with this. I am working on a project management app in Google App Maker. I am using SQL data models. The portion of the app I am having trouble with is several project detail pages linked with a project list page. I have no trouble going from the project list page to the first project detail page.
I have a table widget in the project list page, and the onClick code for a table row is:
app.datasources.Projects.selectKey(widget.datasource.item._key);
app.showPage(app.pages.ProjectPage);
This filters the first detail page with the primary key of the record in focus. No problem here. When I want to navigate the next detail page that has a different datasource (but I still need to filter using the same primary key) I am struggling.
If someone can guide me in making a button in the first detail page that when clicked gets the _key of the project loaded in that current page and filters another detail page for the same project in focus, (using another data model, let's call it ProjectBudgetPage for example) I would really appreciate the help.
This was one my latest attempts at navigation to other detail pages within a project, this was for an onClick event:
var widgets = widget.parent.descendants;
var projectID = widgets.HiddenProjectID.text;
var projectDataSource = app.datasources.ProjectBudgetView.item;
projectDataSource = projectID;
app.showPage(app.pages.ProjectInfoPage);
which results in: (TypeError) : Cannot read property '__gwt_instance' of undefined
at HeaderProjectTabs.HeaderContainer.InfoTabButton.onClick:5:5
I am not sure if I understood properly but I suggest that you start by checking this official App Maker template available here https://developers.google.com/appmaker/templates/partner-management/ which provides a ready-to-run app you can use to manage an internal list of partners, but you can also customize it with your own needs. I recently used most of the UI and logic of this template and I was able to integrate it with SQL data models and it works really great.
I noticed that for this template they are saving the (widget.datasource.item._key) to a Page custom property which you can access afterwards. Custom properties are page-level properties that store data for a single user session. Custom properties are useful when you need to bind properties of multiple widgets to a single value. More info available here https://developers.google.com/appmaker/ui/binding#custom_properties
In your scenario, you can create a new custom property (string) inside your ProjectInfoPage and then you can add a script to the button onClick event in your ProjectPage to save the _key or any other item to a page property. For example:
app.pages.ProjectInfoPage.properties.ProjectKey = widget.datasource.item._key;
app.showDialog(app.pages.ProjectInfoPage);
Then you can get the “ProjectKey” property by adding this to the onAttach event in your ProjectInfoPage:
var key = app.pages.ProjectInfoPage.properties.ProjectKey;
…
In the onAttach event you can filter the new model or pass the property values to a server script. I believe there are other approaches but I hope this helps!
I would like to add custom right clicks to a number of spark list controls.
I have tried the following as an item renderer. (as per the flex 4 cook book).
Full Render code here http://pastebin.com/Kx8tJ1cY
When I right click on the Spark List I simply get the Adobe Default Context menu.
This is the same default behaviour I had before I added any code to this.
Could anyone tell me how to add right clicks to List Items in Flex 4.
Please and Thank you.
I found the problem/solution. You cant use context menus if there are Vboxes or Tab Navigators. Which is insane because it means I cant do relative layout properly or decent variable width design.
Quoted from: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/ui/ContextMenu.html
For example, if a DataGrid control is a child of a TabNavigator or VBox container, the DataGrid control cannot have its own context menu.
Christopher Huyler posted something similar (source code available here). From the article:
Start out by grabbing the Javascript code from Google's code repository.
Step 1 – Setup custom context menu code
Create a new Flex project in Flex Builder. Copy rightclick.js and swfobject.js into the html-template folder of your project. From here, I had to make several changes…
I modified the RightClick.init() function to accept an object and container value as input. This allows me to pass in the name of the application as the object instead of having it be called the same thing every time.
I included rightclick.js and swfobject.js in the header of index.template.html.
I added a new div to the body called “flashcontent”.
I added an onload handler to the body tag to initialize RightClick
I replaced AC_FL_RunContent(…) with new SWFObject(…) making sure to maintain all template variables.
After making these changes, I verified that no right-click context menu appears in my application.
Step 2 – Listen for the rightClick event
Next I added a few lines to the main mxml file of my application to listen for the ExternalInterface event that will be dispatched when I right-click my appliction.
private function handleCreationComplete():void
{
ExternalInterface.addCallback("rightClick", handleRightClick);
}
private function handleRightClick():void
{
Alert.show("Right Click Success!");
}
Step 3 – Dispatch an event to the correct object
Getting the event to the main application is easy, but we actually want the appropriate child object to be notified when the right-click event occurs. Since I am not using any double-click events in my application I decided I would treat every right-click event like a double-click event. Users without a two button mouse (aka Mac users) can simply double-click to get the same menu while users with a two button mouse just have to right-click. Here is how I make sure the event is dispatched to the appropriate object.
private function handleRightClick():void
{
var objects:Array = systemManager.getObjectsUnderPoint(
new Point(mouseX,mouseY));
if (objects.length>0)
{
var object:Object = objects[objects.length-1];
var relatedObject:InteractiveObject;
if (object is InteractiveObject)
relatedObject = object as InteractiveObject;
else if (object.parent && object.parent is InteractiveObject)
relatedObject = object.parent;
var event:MouseEvent = new MouseEvent(
MouseEvent.DOUBLE_CLICK,true,false,mouseX,mouseY,
relatedObject);
object.dispatchEvent(event);
}
}
I hope this helps!