JointJs initializing custom element - javascript

I have the following custom element:
var ir = joint.dia.Element.define('my.Rectangle', {
attrs: {
body: {
// ...
},
header: {
// ...
}
}
}, {
initialize: function() {
this.on("change:header", function() {
console.log('header change')
}, this), joint.dia.Element.prototype.initialize.apply(this, arguments)
},
markup: [{
tagName: 'rect',
selector: 'body',
}, {
tagName: 'text',
selector: 'header'
}]
});
I want to break the header's text with joint.util.breakText and set the body's size, to fit in it, every time it changes. (Even first time it set)
After
var rect = new joint.shapes.my.Rectangle()
rect.attr('header/text', 'FooBarBaz')
rect.addTo(graph);
nothing happens, the shape is added to the screen, but nothing in the console log.

change:header will listen to changes in the header property. To listen to this, use rect.prop instead of rect.attr:
rect.prop('header', 'FooBarBaz')
From the docs, at http://resources.jointjs.com/docs/jointjs/v2.2/joint.html#dia.Element.prototype.prop:
This is an equivalent of the attr() method but this time for custom data properties.
To listen to attribute changes, use change:attrs:
this.on('change:header', function (element, opt) {
if (opt.propertyPath === 'attrs/header/text') {
console.log('header change');
}
}, this), joint.dia.Element.prototype.initialize.apply(this, arguments);

Related

How to get codemirror 'change' event to fire in Grapesjs plugin

I'm building a Grapesjs plugin and have added a 'jscript' trait to a button component, which appears as a codemirror textarea. The idea is for users to be able to edit some javascript code associated with a button. I can't seem to intercept the codemirror area's change event, at least, not the proper codemirror specific version.
Happily, when I edit the codemirror area and change focus, a regular 'change' event triggers the Grapejs onEvent handler within my plugin's editor.TraitManager.addType('jcodemirror-editor', {} - good. I can then store the contents of the codemirror area into the trait.
onEvent({ elInput, component, event }) {
let code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
},
However if we paste or backspace or delete etc. in the codemirror area then the regular 'change' event is never issued!
So I'm trying to intercept the deeper codemirror specific 'change' event which is usually intercepted via cm.on("change", function (cm, changeObj) {} and which is triggered more reliably (unfortunately also on each keystroke). How do I wire this codemirror specific event to trigger the usual onEvent({ elInput, component, event }) {} code?
I have a workaround in place in my https://jsfiddle.net/tcab/1rh7mn5b/ but would like to know the proper way to do this.
My Plugin:
function customScriptPlugin(editor) {
const codemirrorEnabled = true // otherwise trait editor is just a plain textarea
const script = function (props) {
this.onclick = function () {
eval(props.jscript)
}
};
editor.DomComponents.addType("customScript", {
isComponent: el => el.tagName == 'BUTTON' && el.hasAttribute && el.hasAttribute("data-scriptable"),
model: {
defaults: {
traits: [
{
// type: 'text',
type: 'jcodemirror-editor', // defined below
name: 'jscript',
changeProp: true,
}
],
script,
jscript: `let res = 1 + 3; console.log('result is', res);`,
'script-props': ['jscript'],
},
},
});
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
// This is the 'more accurate' codemirror 'change' event
// which is triggered key by key. We need it cos if we paste
// or backspace or delete etc. in codemirror then the
// regular 'change' event is never issued! But how do we get
// this event to trigger the proper, usual 'onEvent' below?
// Currently cheating and doing the onEvent work here with
// this special handler.
myCodeMirror.on("change", function (cm, changeObj) { // HACK
const component = editor.getSelected()
const code_to_run = myCodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
console.log('onEvent hack - (myCodeMirror change event) updating jscript trait to be:', code_to_run)
})
}
return el;
},
// UI textarea & codemirror 'change' events trigger this function,
// so that we can update the component 'jscript' trait property.
onEvent({ elInput, component, event }) {
let code_to_run
if (codemirrorEnabled)
code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
else
code_to_run = elInput.querySelector('textarea').value
console.log('onEvent - updating jscript trait to be:', code_to_run)
component.getTrait('jscript').set('value', code_to_run);
}, // onEvent
// Updates the trait area UI based on what is in the component.
onUpdate({ elInput, component }) {
console.log('onUpdate - component trait jscript -> UI', component.get('jscript'))
if (codemirrorEnabled) {
const cm = elInput.querySelector(".CodeMirror").CodeMirror
cm.setValue(component.get('jscript'))
// codemirror content doesn't appear till you click on it - fix with this trick
setTimeout(function () {
cm.refresh();
}, 1);
}
else {
const textareaEl = elInput.querySelector('textarea');
textareaEl.value = component.get('jscript')
// actually is this even needed as things still update automatically without it?
// textareaEl.dispatchEvent(new CustomEvent('change'));
}
}, // onUpdate
}) // addType
editor.BlockManager.add(
'btnRegular',
{
category: 'Basic',
label: 'Regular Button',
attributes: { class: "fa fa-square-o" },
content: '<button type="button">Click Me</button>',
});
editor.BlockManager.add(
'btnScriptable',
{
category: 'Scriptable',
label: 'Scriptable Button',
attributes: { class: "fa fa-rocket" },
content: '<button type="button" data-scriptable="true">Run Script</button>',
});
}
const editor = grapesjs.init({
container: '#gjs',
fromElement: 1,
height: '100%',
storageManager: { type: 0 },
plugins: ['gjs-blocks-basic', 'customScriptPlugin']
});
According to official Grapesjs documentation on traits integrating external ui components you can trigger the onEvent event manually by calling this.onChange(ev).
So within createInput I continued to intercept the more reliable myCodeMirror.on("change", ... event and within that handler triggered the onEvent manually viz:
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const self = this // SOLUTION part 1
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
myCodeMirror.on("change", function (cm, changeObj) {
self.onChange(changeObj) // SOLUTION part 2
})
}
return el;
},

Is it possible to call the tippy (js) constructor only on the newest added popover?

after I created the popovers I am calling the tippy() constructor like:
tippy('.my-tippy-popover', { ... }); But this is creating all existing tippy popovers again. So if you have 80 popovers and you add a new one, then the tippy() constructor is creating 81 new popovers. And if you click one of the popovers you get 2x the event e.g. onShow. If you add now again a new popover and run tippy() constructor and click after that a popover, you get 3x onShow event. And so on. So it's adding more and more event listeners.
How can I avoid that?
Edit jsfiddle
jQuery(($) => {
runTippy()
})
function runTippy() {
tippy('.my-tippy-popover', {
content(reference) {
const id = reference.getAttribute('data-template');
const template = document.getElementById(id);
return template.innerHTML;
},
onHide(instance) {
console.log('+++ tippy onHide +++')
},
onCreate(instance) {
console.log('+++ tippy onCreate +++')
},
onShow(instance) {
console.log('+++ tippy onShow +++')
},
onShown(instance) {
console.log('+++ tippy onShown +++')
},
allowHTML: true,
trigger: 'click',
appendTo: document.getElementById('tippy-container'),
interactive: true,
interactiveBorder: 10,
interactiveDebounce: 35,
maxWidth: 'none',
});
}
function addPopover() {
const body = $('body');
body.append(`<span class="my-tippy-popover" data-template="tippyContent" >Popover</span><br>`);
runTippy()
}

Javascript identifier for widget in Odoo

If you have two widget in a view. And you do something with the first widget and you want to update (call display_field) the second widget. How to have the identifier for the second widget?
For example in the extend definition of a widget:
local.FieldNewWidget = instance.web.form.AbstractField.extend({
init: function(parent, options) {
},
events: {
'click .oe_new_input_button': 'open_new_specific_form',
},
start: function() {
},
display_field: function() {
},
render_value: function() {
},
open_new_specific_form: function(event) {
var self = this;
var new_action = {
type: 'ir.actions.act_window',
name: $(event.target).data('name'),
res_model: $(event.target).data('data-model'),
res_id: $(event.target).data('res-id'),
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: {
},
flags: {'form': {'action_buttons': true}},
}
self.do_action(new_action, {
on_close: function() {
// I want to refresh (call display_field) the second widget here.
// What is the identifier for the second widget?
},
});
},
});
i think this will work but i don't know if it's the best solution. I think every widget knows witch view it's by using (this.view). why don't you use a special event to trigger it from one widget and listen for it in the other one.
For example Register an event listener on the widget to listen for property changing on the view:
//in first widget register the event listener:
this.view.on('property_name', this, this.your_method);
// in second widget trigger the event by setting the value
this.view.set('property_name', a_value);
i'm new to odoo javascript let me know if this works for you i think there is a better solution by using events triggering without changing properties at all.

using OnClick function in Dojo

I cant seem to figure out how to use it - the following has no output
dom.create(
'a',
{
className: 'collapse',
onClick: function(){
console.log("something");
}
},
topPane.containerNode );
also tried
function testMe(){console.log('something')}
dom.create(
'a',
{
className: 'collapse',
onClick: testMe
},
topPane.containerNode );
Also this:
function testMe(){console.log('something')}
dom.create(
'a',
{
className: 'collapse',
onClick: testMe()
},
topPane.containerNode );
the last one causes testMe to be activated when the page is loaded (and not activated after click)
Try this:
var link = new domConstruct.create("a", {
href: "http://www.google.com",
innerHTML: "Google",
'class': "myClassHere",
onclick: "window.open(this.href); return false;",
onkeypress: "window.open(this.href); return false;"
});
or
var link = new domConstruct.create("a", {
href: "http://www.google.com",
innerHTML: "Google",
'class': "myClassHere",
onclick: function() { console.log("onclick"); },
onkeypress: function() { console.log("onkeypress"); }
});
I think that onClick is used when dealing with dojo/dijit/dojox widgets. But when setting events for html elements using the dojo/dom-construct, it is all lowercase (ie "onclick").
use dom-attr(domelement,"click",function(){})
it works
dom element is the one on which the click functionality is to be set.
in your example create a using dom construct and then use the above

Is there a nice way to use ui definition inside events in Backbone view

Consider following example:
ui: {
name: '#name'
},
events: {
'change #name' : function() { ... }
}
is there a way so I don't need to write #name selector in both places, since I could have changes in template in future?
After reading your question I got interested and here the result:
It's possible, you just need to move events to intialize like this
ui: {
name: '#name'
},
initialize: function() {
this.events = {};
this.events["change " + this.ui.name] = function() { ... };
}

Categories