Alfresco Object is not available in extension module in alfresco share - alfresco

I am trying to override the javascript controller node-header.js of components\node-details with the extension module of alfresco share
This is my node-header.get.js
<import resource="classpath:/alfresco/templates/org/alfresco/import/alfresco-util.js">
for (var i=0; i<model.widgets.length; i++)
{
if (model.widgets[i].id == "NodeHeader")
{
if(model.widgets[i].options.nodeRef!=null)
{
var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef);
if(jsNode.hasAspect("custom:intranetFile")){
model.widgets[i].options.showFavourite = false;
model.widgets[i].options.showLikes = false;
}
}
}
}
I am getting this error
Error Message: 05270002 Failed to execute script
'classpath*:webscripts/custom/nodeheader/hidelikesync/node-header.get.js':
05270001 ReferenceError: "Alfresco" is not defined.
(jar:file:/C:/Alfresco/Alfresco42/tomcat/webapps/share/WEB-INF/lib/customshare.jar!/webscripts/custom/nodeheader/hidelikesync/node-header.get.js#1555)
Error lies in this line
var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef);
as Alfresco object is not available how can I get it?

Based on my answer yesterday on the share-extras-devel list:
Your issue is that you are mixing up your web script JS with client-side JavaScript. Alfresco.util.Node is a client-side helper class and is therefore available to client-side JS running in the web browser, but not to your web script code which runs on the server.
If you look at the source of alfresco-util.js, which you are including, you will see that there is a helper class there but it is called AlfrescoUtil.
To get some information on this given node I would suggest that you want to use the static method AlfrescoUtil.getNodeDetails() from that class, e.g.
var jsNode = AlfrescoUtil.getNodeDetails(model.widgets[i].options.nodeRef);
The structure of the jsNode object will be as per the JSON returned by the doclist-v2 webscripts, so you should be able to check for the presence of your custom aspect in the aspects array property.
If you check the source of alfresco-util.js you will see that additional parameters are also supported by getNodeDetails(). It seems to me you can also pass in an optional site name, plus some options if you wish.

Related

Download an externalReference from PlannerTask using the Graph API

I'd like to download a file attached to a PlannerTask. I already have the external references but I can't figure out how to access the file.
An external reference is a JSON object like this:
{
"https%3A//contoso%2Esharepoint%2Ecom/sites/GroupName/Shared%20Documents/AnnualReport%2Epptx":
{
// ... snip ...
}
}
I've tried to use the following endpoint
GET /groups/{group-id}/drive/root:/sites/GroupName/Shared%20Documents/AnnualReport%2Epptx
but I get a 404 response. Indeed, when I use the query in Graph Explorer it gives me a warning about "Invalid whitespace in URL" (?).
A workaround that I've found is to use the search endpoint to look for files like this:
GET /groups/{group-id}/drive/root/search(q='AnnualReport.pptx')
and hope the file name is unique.
Anyway, with both methods I need extra information (ie. the group-id) that may not be readily available by the time I have the external reference object.
What is the proper way to get a download url for a driveItem that is referenced by an external reference object in a PlannerTask?
Do I really need the group-id to access such file?
The keys in external references are webUrl instances, so they can be used with the /shares/ endpoint. See this answer for details on how to do it.
When you get a driveItem object, the download url is available from AdditionalData: driveItem.AdditionalData["#microsoft.graph.downloadUrl"]. You can use WebClient.DownloadFile to download the file on the local machine.
Here is the final code:
var remoteUri = "https%3A//contoso%2Esharepoint%2Ecom/sites/GroupName/Shared%20Documents/AnnualReport%2Epptx";
var localFile = "/tmp/foo.pptx";
string sharingUrl = remoteUri;
string base64Value = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sharingUrl));
string encodedUrl = "u!" + base64Value.TrimEnd('=').Replace('/','_').Replace('+','-');
DriveItem driveItem = m_graphClient
.Shares[encodedUrl]
.DriveItem
.Request()
.GetAsync().Result;
using (WebClient client = new WebClient())
{
client.DownloadFile(driveItem.AdditionalData["#microsoft.graph.downloadUrl"].ToString(),
localFile);
}

How to send a POST request from GTM custom tag template?

I'm developing a simple custom tag template for Google Tag Manager. It's supposed to bind to some events and send event data to our servers as JSON in the body of a POST request.
The sandboxed GTM Javascript runtime provides the sendPixel() API. However, that only provides GET requests.
How one sends a POST request from within this sandboxed runtime?
You can use a combination of the injectScript and copyFromWindow APIs found here Custom Template APIs.
Basically, the workflow goes like this.
Build a simple script that contains a function attached to the window object that sends a normal XHR post request. The script I made and use can be found here: https://storage.googleapis.com/common-scripts/basicMethods.js
Upload that script somewhere publically accessible so you can import it into your template.
Use the injectScript API to add the script to your custom template.
The injectScript API wants you to provide an onSuccess callback function. Within that function, use the copyWindow api to grab the post request function you created in your script and save it as a variable.
You can now use that variable to send a post request the same way you would use a normal JS function.
The script I included above also includes JSON encode and Base64 encode functions which you can use the same way via the copyWindow api.
I hope that helps. If you need some specific code examples for parts I can help.
According to #Ian Mitchell answer - I've made similar solution.
This is the basic code pattern that can be used inside GTM template code section in such as scenario:
const injectScript = require('injectScript');
const callInWindow = require('callInWindow');
const log = require('logToConsole');
const queryPermission = require('queryPermission');
const postScriptUrl = 'https://myPostScriptUrl'; //provide your script url
const endpoint = 'https://myEndpoint'; //provide your endpoint url
//provide your data; data object contains all properties from fields tab of the GTM template
const data = {
sessionId: data.sessionId,
name: data.name,
description: data.description
};
//add appropriate permission to inject script from 'https://myPostScriptUrl' url in GTM template's privileges tab
if (queryPermission('inject_script', postScriptUrl)) {
injectScript(postScriptUrl, onSuccess, data.gtmOnFailure, postScriptUrl);
} else {
log('postScriptUrl: Script load failed due to permissions mismatch.');
data.gtmOnFailure();
}
function onSuccess() {
//add appropriate permission to call `sendData` variable in GTM template's privileges tab
callInWindow('sendData', gtmData, endpoint);
data.gtmOnSuccess();
}
It's important to remember to add all necessary privillages inside GTM template. Appropriate permissions will show automatically in privillages tab after use pertinent options inside code section.
Your script at 'https://myPostScriptUrl' may looks like this:
function sendData(data, endpoint) {
var xhr = new XMLHttpRequest();
var stringifiedData = JSON.stringify(data);
xhr.open('POST', endpoint);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(stringifiedData);
xhr.onload = function () {
if (xhr.status.toString()[0] !== '2') {
console.error(xhr.status + '> ' + xhr.statusText);
}
};
}
It is not strictly necessary to load an external script. While still a workaround, you can also pass a fetch reference into the tag through a "JavaScript Variable" type variable:
Create a GTM variable of type "JavaScript Variable" with the content "fetch", thus referencing "window.fetch"
Add a text field to your Custom Tag, e. g. named "js.fetchReference".
Use data.fetchReference in your Custom Tag's like you normally would use window.fetch
Make sure the tag instance actually references the variable created in step 2 with {{js.fetchReference}}
I jotted this down with screenshots at https://hume.dev/articles/post-request-custom-template/

Injecting precompiled handlebars template in Handlebars.template method

Hi I am currently using Express Handlebars setup [https://github.com/ericf/express-handlebars][1] to render compiled and pre-compiled templates. In case of pre-compilation I have configured express middleware to get handlebars templates as precompiled and manually configured registration of handlebars pre-compiled templates like this:
hbs.getTemplates('views/partials/page/', {
cache : app.enabled('view cache'),
precompiled: true
}).then(function (templates) {
var templatesArr = hbs.handlebars.templates = hbs.handlebars.templates || {}
// extract all templates names and values
templates = Object.keys(hbs.handlebars.templates).map(function (name) {
//get precompiled template data
var templateSpec = templates[name]
//Inject precompiled data into Handlebars.template method
var precompileTemplateRef = hbs.handlebars.template(templateSpec)
//templateSpec is treated as String rather than Object
//and this is where the things break because
//handlebars.template expects Object
//Register precompileTemplateRef with Handlebars so that
//precompiled template can be extracted for later use by using
//template name
templatesArr[name] = precompileTemplateRef
});
When I run the express server, hbs.handlebars.template doesn't get executed because templateSpec mentioned above is received as a string rather than object.
Even using JSON.parse(templateSpec) doesn't work because precompiled output is not JSON BUT an Object literal.
Precompiled output looks something like this:
{"1":function(container,depth0,helpers,partials,data) {var helper;ret.......
I want to know if its possible to inject just the pre-compiled output at run time into Handlebars.template(templateSpec) OR Do I have to create precompiled template in file system which has registration with Handlebars.templates.
In case of compiled templates there are no issues.
Many thanks in advance
This is an open issue with Handlebars as per the following links:
https://github.com/wycats/handlebars.js/issues/922
https://github.com/wycats/handlebars.js/issues/1033
var templateSpec = (new Function('return ' + templates[name])());
Will resolve the problem

Accessing "customconfiguration" inside GUI Extension

I'm doing a GUI Extension of User Interface (SiteEdit) by overriding the behaviour of one of the javascript files, to add some funcionality.
The javascript file is "/Scripts/Components/ExtComponentField.js" and the target is "SiteEdit" extending:
Tridion.Web.UI.Editors.SiteEdit.Views.Content
All works well with the extension, and I have what I wanted to have, but now I'm trying to use the
settings/customconfiguration/clientconfiguration
node of the extension config, to use some initialization parameters, but there is no way to access $config element in the javascript, and Tridion.Core.Configuration.Editors["myExt"].configuration is null.
I've seen using this customconfiguration in various javascripts like "Dashboard" or "Footprints", but is it possible to have it on "Content"? am I missing something on the extension config?
I'm afraid I didn't test this but you should be able to use:
Extensions.YourExt.getConfigurationItem = function (itemName, editorName)
{
var editor = $config.Editors[editorName].configuration;
if (editor)
{
var confXml = $xml.getNewXmlDocument(editor);
var confObj = $xml.toJson(confXml);
if (confObj[itemName])
return confObj[itemName];
else
return "";
}
}
You can then use it in the following way:
$this.getConfigurationItem("YOUR_CONFIG_ITEM_NAME", "YOUR_EDITOR_NAME").toString();
In your extension configuration (below the <theme> node) you can enter your own configuration values:
<customconfiguration>
<clientconfiguration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge">
<YOUR_CONFIG_ITEM_NAME>The value</YOUR_CONFIG_ITEM_NAME>
Can you confirm :)
I usually use a separate JS file with the following:
Type.registerNamespace("Extensions.Namespace");
Extensions.Namespace.getEditorConfigSection = function Editor$getEditorConfigSection() {
if (this._settings === undefined) {
var editor = $config.Editors["ThisEditorName"];
if (editor && editor.configuration && !String.isNullOrEmpty(editor.configuration)) {
var configSectionXmlDoc = $xml.getNewXmlDocument(editor.configuration);
this._settings = $xml.toJson(configSectionXmlDoc.documentElement);
}
}
return this._settings;
};
and in the configuration add it in a separate group:
<cfg:group name="Extensions.Namespace" merge="always">
<cfg:fileset>
<cfg:file type="script">/Scripts/Definitions.js</cfg:file>
</cfg:fileset>
</cfg:group>
Then wherever you need it, you can add the following dependency:
<cfg:dependency>Extensions.Namespace</cfg:dependency>
Then I usually use a function like this to get a certain configuration value:
Extensions.Namespace.Something.prototype._getMyConfigValue = function Something$_getMyConfigValue() {
var configSection = Extensions.Namespace.getEditorConfigSection();
if (configSection) {
return configSection.myconfigvalue;
}
};
The code contained in the "Content" group is running inside of the IFRAME which is hosting your published web page. As you can imagine, the amount of files included there should be minimized and so quite a lot of functionality is not available.
My suggestion would be to read the configuration only in the main window and then pass along the settings that you need to the code running in the IFRAME -- through the use of the Tridion.Utils.CrossDomainMessaging utility class ($xdm).

FileReference.load() does not as excepted

I used Flash player 10, and Flex SDK 3.4. The code as followings:
// Following comes callbacks
function imageLoadOpenCallback(evt:Event):void
{
trace("in--open");
}
function imageLoadCompleteCallback(evt:Event):void
{
trace("in--load");
var fr:FileReference = evt.target as FileReference;
trace(fr.data);
}
function imageLoadErrorCallback(evt:IOErrorEvent):void
{
trace("in--ioerror");
}
function imageSelectCancelCallback(evt:Event):void
{
trace("in cancel");
}
function imageSelectCallback(evt:Event):void
{
trace("in -- select");
for (var i:int=0; i<frl.fileList.length; i++)
{
frl.fileList[i].addEventListener(Event.OPEN, imageLoadOpenCallback);
frl.fileList[i].addEventListener(Event.COMPLETE, imageLoadCompleteCallback);
frl.fileList[i].addEventListener(IOErrorEvent.IO_ERROR, imageLoadErrorCallback);
frl.fileList[i].load();
trace(frl.fileList[i]);
trace(frl.fileList[i].creationDate);
trace(frl.fileList[i].creator);
trace(frl.fileList[i].data);
trace(frl.fileList[i].name);
}
}
// Following comes UI handlers
function onAddPictures():void
{
var imageFilter:FileFilter = new FileFilter("Images", "*.jpg;*.png");
frl.addEventListener(Event.SELECT, imageSelectCallback);
frl.addEventListener(Event.CANCEL, imageSelectCancelCallback);
frl.browse([imageFilter]);
}
Only the imageSelectCancelCallback handler get called when I select some files in the dialog. But no load/open/io_error handler get called at all. I have Google some code example, in which it used FileReference instead of FileReferenceList. I don't know the reason, could you please help me?
In Air the fileReference objects in fileReferenceList do not fire the complete event when doing fileList[i].load(). In a Flex project it works fine. Adobe has not responded to bug reports on this appropriately.
Make sure in your compiler settings for flex, that you have at least 10.0.0 for "Use a specific version".
The main reason to use FileReferenceList instead of FileReference would be if you need to upload multiple files at once. If you only want to allow uploading one file at once, simply use FileReference.
Some clarification: imageSelectCallback(), and NOT imageSelectCancelCallback(), should get called when you select some files in the file browser AND click OK. imageSelectCancelCallback() is only called when you click Cancel.
Other than that, I never used the load() API, but I did use the upload(URLRequest) API. I am not sure what's your use case, but if you need to upload an image to a server, you should use the upload() method.
Speaking of upload events, I experienced some reliability issues when listening to Event.COMPLETE events, so I actually got better results listening to DataEvent.UPLOAD_COMPLETE_DATA.

Resources