Javascript inside a xwiki macro have no effect - xwiki

I am trying to write a macro, which embed draw.io. when user double click the diagram image I want to open draw.io in aonther window and get image data back as text. but my javascript inside macro not working at all.
It seems my javascript code in macro is not executed at all, can somebody tell me the right way to inject javascrpt code inside a velocity macro?
updates: I have noticed that when editing in CKEditor, my javascript code will not be executed, bu when editing in inline editor it will be executed.
{{velocity}}
$wikimacro.content
#if($wikimacro.content == '')
#set ($wikimacro.content = 'iVBORw0KGgoAAAANSUhEUgAAAIMAAABHCAYAAAAk5PTEAAAC4HRFWHRteGZpbGUAJTNDbXhmaWxlJTIwaG9zdCUzRCUyMjE3Mi4yNS4xNjEuMjExJTIyJTIwbW9kaWZpZWQlM0QlMjIyMDIxLTExLTA4VDA3JTNBNDglM0EzMC4wMzdaJTIyJTIwYWdlbnQlM0QlMjI1LjAlMjAoWDExKSUyMiUyMGV0YWclM0QlMjJ4MTM1OUpQR29sZ21udEFCTFIxSCUyMiUyMHZlcnNpb24lM0QlMjIxNS42LjIlMjIlMjB0eXBlJTNEJTIyZGV2aWNlJTIyJTNFJTNDZGlhZ3JhbSUyMGlkJTNEJTIyNEd0aVp4cWRuemJnanpmMC0zXzElMjIlMjBuYW1lJTNEJTIyUGFnZS0xJTIyJTNFalpKTlQ4UWdFSVolMkZUWThtTFdqdFhsMjdidHg0cXNaNEpHVXNKTFEwTEYxYWY3MVVwbCUyRlpiT0tKNFprUFp0NGhvdnU2ZnpHc0ZXJTJCYWc0cEl6UHVJUGtlRWtEZ2wlMkZoakpFRWlTa0N5UXlraU9iQUdGJTJGQUdFTWRKT2NqaHZBcTNXeXNwMkMwdmRORkRhRFdQR2FMY04lMkI5WnElMkIyckxLcmdDUmNuVU5mMlUzSXBBTSUyRks0OENQSVNrd3ZKJTJCa3VlR28yQmVNa1o4RzRkaXRFODRqdWpkWTJXSFclMkZCeldxTiUyQmtTOGc0M3ZITmpCaHI3bjRRRFl6S1Z1OVBISlQ5JTJCdVpOOUw5enJIVmE1TU5YaHdOaXNIU1lGak80YURtT1JPS0pQVGtnTFJjdkswZXY4MGowVHRsYiUyQmxuZ1R5NEd4ME4lMkZzTTVtbjklMkY4R2RBM1dERDRFRSUyQmpEZlVqQkwwTXpGTkF0JTJCaWVUcUdLbGZZcU00Y3FydWZTaWlqZFFtT202TE9EUHQlMkZySE5QOEYlM0MlMkZkaWFncmFtJTNFJTNDJTJGbXhmaWxlJTNFUXJoXAAAAKNJREFUeJzt0rENACEQwDD2X5rvUoOe6mRL2SBrAQDAA1vjO7aZywzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzkegaNDwAA/vkAmk+zV9RvOdkAAAAASUVORK5CYII=')
#end
#if($xcontext.action == 'edit')
{{html}}
<img
class="drawio"
style="cursor: default"
src="data:image/png;base64,$wikimacro.content"
/>
<script>
//<![CDATA[
document.addEventListener('dblclick', function(evt)
{
var url = 'http://172.25.161.211/?embed=1&ui=atlas&spin=1&modified=unsavedChanges&proto=json';
var source = evt.srcElement || evt.target;
if (source.nodeName == 'IMG' && source.className == 'drawio'){
if (source.drawIoWindow == null || source.drawIoWindow.closed) {
// Implements protocol for loading and exporting with embedded XML
var receive = function(evt) {
if (evt.data.length > 0 && evt.source == source.drawIoWindow) {
var msg = JSON.parse(evt.data);
// Received if the editor is ready
if (msg.event == 'init') {
// Sends the data URI with embedded XML to editor
source.drawIoWindow.postMessage(JSON.stringify({action: 'load', xmlpng: source.getAttribute('src')}), '*');
}
// Received if the user clicks save
else if (msg.event == 'save')
{
// Sends a request to export the diagram as XML with embedded PNG
source.drawIoWindow.postMessage(JSON.stringify(
{action: 'export', format: 'xmlpng', spinKey: 'saving'}), '*');
}
// Received if the export request was processed
else if (msg.event == 'export')
{
// Updates the data URI of the image
// TODO save to xwiki server as macro's content
source.setAttribute('src', msg.data);
}
// Received if the user clicks exit or after export
if (msg.event == 'exit' || msg.event == 'export')
{
// Closes the editor
window.removeEventListener('message', receive);
source.drawIoWindow.close();
source.drawIoWindow = null;
}
}
};
// Opens the editor
window.addEventListener('message', receive);
source.drawIoWindow = window.open(url);
}
else
{
// Shows existing editor window
source.drawIoWindow.focus();
}
}
});
// ]]>
</script>
{{/html}}
#else
{{html}}
<img src="data:image/png;base64,$wikimacro.content" />
{{/html}}
#end
{{/velocity}}

In XWiki the insertion of web resources like css and js content is generally done separately through what is called "skin extensions", best is probably to look at the documentation on https://extensions.xwiki.org/xwiki/bin/view/Extension/Skin%20Extension%20Plugin.
In short, it involves adding an object of type XWiki.JavaScriptExtention in the page holding your macro and reference to it in your macro's code.
Note that an extension which integrate draw.io already exist on https://extensions.xwiki.org/xwiki/bin/view/Extension/Diagram%20Application, and I guess looking at the source of this extension might be interesting.

Related

How to prevent closing of cell edit mode on validation errors with custom vue components in ag-grid

I have succesfully rendered my own component as the cellEditor and would like and on-leave I would like it to try to validate the value and prevent the closing if it fails.
If I look at this then https://www.ag-grid.com/javascript-grid-cell-editing/#editing-api there's cancelable callback functions for editing. But in this callback function is there a way to access the current instantiated component? I would think that would be the easiest way to handle this.
I'm using vee-validate so the validation function is async, just to keep in mind.
Use Full row editing.
Create a global variable like
var problemRow = -1;
Then Subscribe to this events:
onRowEditingStarted: function (event) {
if (problemRow!=-1 && event.rowIndex!=problemRow) {
gridOptions.api.stopEditing();
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
},
onRowEditingStopped: function (event) {
if (problemRow==-1) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
}
if (problemRow == event.rowIndex) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
else{
problemRow=-1;
}
}
},
I had a similar issue - albeit in AngularJS and the non-Angular mode for ag-grid - I needed to prevent the navigation when the cell editor didn't pass validation.
The documentation is not very detailed, so in the end I added a custom cell editor with a form wrapped around the input field (to handle the niceties such as red highlighting etc), and then used Angular JS validation. That got me so far, but the crucial part was trying to prevent the user tabbing out or away when the value was invalid so the user could at least fix the issue.
I did this by adding a value parser when adding the cell, and then within that if the value was invalid according to various rules, throw an exception. Not ideal, I know - but it does prevent ag-grid from trying to move away from the cell.
I tried loads of approaches to solving this - using the tabToNextCell events, suppressKeyboardEvent, navigateToNextCell, onCellEditingStopped - to name a few - this was the only thing that got it working correctly.
Here's my value parser, for what it's worth:
var codeParser = function (args) {
var cellEditor = _controller.currentCellEditor.children['codeValue'];
var paycodeId = +args.colDef.field;
var paycodeInfo = _controller.paycodes.filter(function (f) { return f.id === paycodeId; })[0];
// Check against any mask
if (paycodeInfo && paycodeInfo.mask) {
var reg = new RegExp("^" + paycodeInfo.mask + '$');
var match = args.newValue.match(reg);
if (!match) {
$mdToast.show($mdToast.simple().textContent('Invalid value - does not match paycode format.').position('top right').toastClass('errorToast'))
.then(function(r) {
_controller.currentCellEditor.children['codeValue'].focus();
});
throw 'Invalid value - does not match paycode format.';
}
}
return true;
};
The _controller.currentCellEditor value is set during the init of the cell editor component. I do this so I can then refocus the control after the error has been shown in the toast:
CodeValueEditor.prototype.init = function (params) {
var form = document.createElement('form');
form.setAttribute('id', 'mainForm');
form.setAttribute('name', 'mainForm');
var input = document.createElement('input');
input.classList.add('ag-cell-edit-input');
input.classList.add('paycode-editor');
input.setAttribute('name', 'codeValue');
input.setAttribute('id', 'codeValue');
input.tabIndex = "0";
input.value = params.value;
if (params.mask) {
input.setAttribute('data-mask', params.mask);
input.setAttribute('ng-pattern','/^' + params.mask + '$/');
input.setAttribute('ng-class',"{'pattern-error': mainForm.codeValue.$error.pattern}");
input.setAttribute('ng-model', 'ctl.currentValue');
}
form.appendChild(input);
this.container = form;
$compile(this.container)($scope);
_controller.currentValue = null;
// This is crucial - we can then reference the container in
// the parser later on to refocus the control
_controller.currentCellEditor = this.container;
$scope.$digest();
};
And then cleared in the grid options onCellEditingStopped event:
onCellEditingStopped: function (event) {
$scope.$apply(function() {
_controller.currentCellEditor = null;
});
},
I realise it's not specifically for your components (Vue.js) but hopefully it'll help someone else. If anyone has done it a better way, I'm all ears as I don't like throwing the unnecessary exception!

Change ICN contentViewer's tab title in split pane mode?

I need to change the "title" for each document shown in ICN Viewer, dynamically, at runtime. I'll read the new viewer tab title from the document properties
ENVIRONMENT: ICN 2.0.3 CM8.5 WAS 8.5.5
CODE SO FAR:
I found a PARTIAL solution by hooking "ecm.model.desktop, onChange":
aspect.after(ecm.model.desktop, 'onChange', function() {
var contentViewer = dijit.byId('contentViewer');
if (contentViewer) {
var viewerTabTitleDef = new ViewerTabTitleDef ();
contentViewer.mainTabContainer.getChildren().forEach(function(child) {
viewerTabTitleDef.changeTitle(viewerTabTitleDef.self,
child.controlButton, child.contentViewerPane.viewerItem.item);
});
...
I was able to extend this for subsequent documents opened in the same viewer, and optimized by "removing()" the handler after this initial call. Here is the complete code:
var kill = aspect.after(ecm.model.desktop, 'onChange', function() {
var contentViewer = dijit.byId('contentViewer');
// "contentViewer" will be "unknown" unless viewer invoked
console.log('onChange: contentViewer', contentViewer);
if (contentViewer) {
console.log("new ViewerTabTitleDef()...");
kill.remove();
var viewerTabTitleDef = new ViewerTabTitleDef ();
contentViewer.mainTabContainer.getChildren().forEach(function(child) {
// For initially opened tabs
console.log('initially opened: child', child);
viewerTabTitleDef.changeTitle(viewerTabTitleDef.self, child.controlButton, child.contentViewerPane.viewerItem.item);
});
aspect.after(contentViewer.mainTabContainer, 'addChild', function(child) {
// For tabs added after the viewer was opened
console.log('subsequently opened: child', child);
viewerTabTitleDef.changeTitle(viewerTabTitleDef, child.controlButton, child.contentViewerPane.viewerItem.item);
}, true);
} // end if contentViewer
}); // end aspect.after(onChange desktop)
CURRENT PROBLEM:
Q: How can I change the label for a split tab (either vertical or horizontal)?
So far, I have NOT been able to find any event for any ICN/ECM widget or object variable that I can trigger on.
Thank you in advance!
===============================================
ADDENDUM:
Many thanks to Ivo Jonker, for his suggestion to modify the widget prototype's
"getHtmlName()" method. It worked!
Specifically:
I'm invoking this code from an ICN plugin. I set event handlers in my plugin's base .js file, but it actually gets invoked in the new, separate viewer window.
The original prototype looked like this:
getHtmlName: function() {
var methodName = "getHtmlName";
this.logEntry(methodName);
var displayName = this.item.getDisplayValue("{NAME}");
if (displayName == "") {
displayName = this.item.name;
}
var htmlName = entities.encode(displayName);
this.logExit(methodName);
return htmlName;
},
Per Ivo's suggestion, I overrode the prototype method like this:
myPluginDojo.viewerTabTitleDef = viewerTabTitleDef;
...
ecm.widget.viewer.model.ViewerItem.prototype.getHtmlName = function () {
console.log("NEW getHtmlName()...");
var displayName = myPluginDojo.viewerTabTitleDef.getTitle(this.item);
return displayName;
};
If i understand you correctly, you want to show a different tab-title (instead of the document title) in the navigator viewer whenever a doc is opened?
How about this:
Every document you open in the viewer is wrapped in a ecm.widget.viewer.model.ViewerItem which exposes the getHtmlName that returns the name used in the tab.
Your solution would be to implement your own getHtmlName.
Unfortunately though, the ViewerItem is constructed in the ecm.widget.viewer.ContentViewer#_open and then passed to the ecm.widget.viewer.ContentViewer#_openTab. So you'll either violate best practice by mingling with IBM private method's, or you'll go for a generic approach and just replace the ecm.widget.viewer.model.ViewerItem.prototype.getHtmlName

How do I display invalid form Dijits inside closed TitlePanes?

I have a large Dijit-based form with many Dijits in collapsible TitlePanes.
When the form validates, any invalid items hidden inside closed TitlePanes (obviously) cannot be seen. So it appears as though the form is just dead and won't submit, though, unbeknownst to the user, there's actually an error hidden in a closed TitlePane which is preventing the form processing.
What's the solution here? Is there an easy way to simply open all TitlePanes containing Dijits that are in an error state?
If validation is done by following, it will work:-
function validateForm() {
var myform = dijit.byId("myform");
myform.connectChildren();
var isValid = myform.validate();
var errorFields = dojo.query(".dijitError");
errorFields.forEach(fieldnode){
var titlePane = getParentTitlePane(fieldnode);
//write a method getParentTitlePane to find the pane to which this field belongs
if(titlePane) {
titlePane.set('open',true);
}
}
return isValid;
}
function getParentTitlePane(fieldnode) {
var titlePane;
//dijitTitlePane is the class of TitlePane widget
while(fieldnode && fieldnode.className!="dijitTitlePane") {
fieldnode= fieldnode.parentNode;
}
if(fieldnode) {
mynode = dijit.getEnclosingWidget(fieldnode);
}
return titlePane;
}
Lets say if the following is the HTML and we call the above validateForm on submit of form.
<form id="myform" data-dojo-type="dijit/form/Form" onSubmit="validateForm();">
......
</form>
Here's what I ended up doing (I'm not great with Javascript, so this might sucked, but it works -- suggestions for improvement are appreciated):
function openTitlePanes(form) {
// Iterate through the child widgets of the form
dijit.registry.findWidgets(document.getElementById(form.id)).forEach(function(item) {
// Is this a title pane?
if (item.baseClass == 'dijitTitlePane') {
// Iterate the children of this title pane
dijit.registry.findWidgets(document.getElementById(item.id)).forEach(function(child) {
// Does this child have a validator, and -- if so -- is it valid?
if (!(typeof child.isValid === 'undefined') && !child.isValid()) {
// It's not valid, make sure the title pane is open
item.set('open', true);
}
});
}
});
}

DOJO ContentPane.set("href", "..." ) not loading content

I have a ContentPane defined as follows:
<div id="searchResultsContentPane" data-dojo-type="dijit.layout.ContentPane" data-dojo-props='splitter:false, region:"center"'></div>
I am trying to dynamically set the href when a button in another ContentPane is pressed:
var searchResultsContentPane = dijit.byId("searchResultsContentPane");
searchResultsContentPane.set("href", "modules/content_panes/callrecords.php");
For some reason this doesn't seem to be working. The content pane flashes loading then goes back to white and FireBug doesn't give me usable info. This is all it shows:
If you cant read that it says in red:
GET http://cdr.homelinux.net:10001/Mike/modules/content_panes/callrecords.php
callrecords.php loads just fine if I set it with html as a data-dojo-props property.
Thanks
Page was refreshing. Used the following code to properly load the content pane.
function sendSearchForm() {
// format taken from http://dojotoolkit.org/reference-guide/1.7/dojo/xhrPost.html
var form = dojo.byId("search_form");
dojo.connect(form, "onsubmit", function(event) {
dojo.stopEvent(event);
var xhrArgs = {
form: dojo.byId("search_form"),
handleAs: "text",
load: function(data){
loadAdvancedSearchResultsTable();
//var searchResultsContentPane = dijit.byId("searchResultsContentPane");
//searchResultsContentPane.set("href", "modules/content_panes/test_module.html");
},
error: function(error){
// TODO Handle errors
}
}
// Call the asynchronous xhrPost
//dojo.byId("response").innerHTML = "Form being sent..."
var deferred = dojo.xhrPost(xhrArgs);
});
}
dojo.ready(sendSearchForm);

Yii renderpartial (proccessoutput = true) Avoid Duplicate js request

Im creating a site who works with ajaxRequest, when I click a link, it will load using ajaxRequest. When I load for example user/login UserController actionLogin, I renderPartial the view with processOUtput to true so the js needed inside that view will be generated, however if I have clientScriptRegister inside that view with events, how can I avoid to generate the scriptRegistered twice or multiple depending on the ajaxRequest? I have tried Yii::app()->clientScript->isSCriptRegistered('scriptId') to check if the script is already registered but it seems that if you used ajaxRequest, the result is always false because it will only be true after the render is finished.
Controller code
if (Yii::app()->request->isAjaxRequest)
{
$this->renderPartial('view',array('model'=>$model),false,true);
}
View Code
if (!Yii::app()->clientScript->isScriptregistered("view-script"))
Yii::app()->clientScript->registerScript("view-script","
$('.link').live('click',function(){
alert('test');
})
");
If I request for the controller for the first time, it works perfectly (alert 1 time) but if I request again for that same controller without refreshing my page and just using ajaxRequest, the alert will output twice if you click it (because it keeps on generating eventhough you already registered it once)
This is the same if you have CActiveForm inside the view with jquery functionality.. the corescript yiiactiveform will be called everytime you renderPartial.
To avoid including core scripts twice
If your scripts have already been included through an earlier request, use this to avoid including them again:
// For jQuery core, Yii switches between the human-readable and minified
// versions based on DEBUG status; so make sure to catch both of them
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery.min.js'] = false;
If you have views that are being rendered both independently and as HTML fragments to be included with AJAX, you can wrap this inside if (Yii::app()->request->isAjaxRequest) to cover all bases.
To avoid including jQuery scripts twice (JS solution)
There's also the possibility of preventing scripts from being included twice on the client side. This is not directly supported and slightly more cumbersome, but in practice it works fine and it does not require you to know on the server side what's going on at the client side (i.e. which scripts have been already included).
The idea is to get the HTML from the server and simply strip out the <script> tags with regular expression replace. The important point is you can detect if jQuery core scripts and plugins have already been loaded (because they create $ or properties on it) and do this conditionally:
function stripExistingScripts(html) {
var map = {
"jquery.js": "$",
"jquery.min.js": "$",
"jquery-ui.min.js": "$.ui",
"jquery.yiiactiveform.js": "$.fn.yiiactiveform",
"jquery.yiigridview.js": "$.fn.yiiGridView",
"jquery.ba-bbq.js": "$.bbq"
};
for (var scriptName in map) {
var target = map[scriptName];
if (isDefined(target)) {
var regexp = new RegExp('<script.*src=".*' +
scriptName.replace('.', '\\.') +
'".*</script>', 'i');
html = html.replace(regexp, '');
}
}
return html;
}
There's a map of filenames and objects that will have been defined if the corresponding script has already been included; pass your incoming HTML through this function and it will check for and remove <script> tags that correspond to previously loaded scripts.
The helper function isDefined is this:
function isDefined(path) {
var target = window;
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return false;
}
target = target[branch];
}
return true;
}
To avoid attaching event handlers twice
You can simply use a Javascript object to remember if you have already attached the handler; if yes, do not attach it again. For example (view code):
Yii::app()->clientScript->registerScript("view-script","
window.myCustomState = window.myCustomState || {}; // initialize if not exists
if (!window.myCustomState.liveClickHandlerAttached) {
window.myCustomState.liveClickHandlerAttached = true;
$('.link').live('click',function(){
alert('test');
})
}
");
The cleanest way is to override beforeAction(), to avoid any duplicated core script:
class Controller extends CController {
protected function beforeAction($action) {
if( Yii::app()->request->isAjaxRequest ) {
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery-2.0.0.js'] = false;
Yii::app()->clientScript->scriptMap['anything.js'] = false;
}
return parent::beforeAction($action);
}
}
Note that you have to put the exact js file name, without the path.
To avoid including script files twice, try this extension: http://www.yiiframework.com/extension/nlsclientscript/
To avoid attaching event handlers twice, see Jons answer: https://stackoverflow.com/a/10188538/729324