Dojo custom component's property gives default always - dojo

I have written a custom component to create a html button.
custom component is defined as follows
dojo.provide("ovn.form.OvnButton") ;
require([ "dojo/_base/declare",
"dojo/dom-construct",
"dojo/parser",
"dojo/ready",
"dijit/_WidgetBase"],
function (declare, domConstruct, parser, ready, _WidgetBase){
return declare ("ovn.form.OvnButton",[_WidgetBase],{
label: "unknown",
constructor : function(args){
this.id = args.id;
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
});
alert("from constructor " + this.label);
},
postMixInProperties : function(){
},
buildRendering : function(){
alert("from renderer label is " + this.label);
this.domNode = domConstruct.create("button", { innerHTML: this.label }); //domConstruct.toDom('<button>' + this.label + '</button>');
},
_getLabelAttr: function(){
return this.label;
},
_setLabelAttr : function(label){
alert("from set input is " + label)
this.label = label;
},
postCreate : function(){
alert("Post create label is " + this.label);
},
startUP : function(){
}
});
});
This is how I am instantiating the component
var button = new ovn.form.OvnButton({
id:'run',
props:[{"name":"label","value":"Run"},{"name":"class","value":"btn"}]
});
In the constructor of the custom component, I am iterating through the array passed and assigning to the instance variable called 'label'. To my surprise when we print the instance variable in buildRendering function, it is still printing the default instead of the assigned value.
can somebody give some light on why this is so.
FYI:
I am getting the following sequence of messages on the console
1.found label Run
2. from constructor unknown
3. from renderer label is unknown
4. from set input is unknown
5. Post create label is unknown

This happens because inside the little forEach function, this actually points to something completely different than your OvnButton object.
Javascript's this keyword is quite strange in this regard (it doesn't have anything to do with Dojo, actually). You can read more about how it works here: http://howtonode.org/what-is-this . It's a quite fundamental concept of Javascript, different from other languages, so it's well worth your time to get familiar with.
But there are various different ways you can quickly solve it, so here are a few!
Use a regular for loop instead of forEach and callback
The easiest is probably to use a regular for loop instead of forEach with a callback.
....
// args.props.forEach(function(prop) {
for(var i = 0, l = args.props.length; i < l; i++) {
var prop = args.props[i];
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}//); <-- no longer need the closing parenthesis
The takeaway here is that Javascript's this magic only happens for function calls, so in this case, when we just use a for loop, this continues to point to the right thing.
... or use forEach's second thisArg argument
But perhaps you really want to use forEach. It actually has a second argument, often called thisArg. It tells forEach to make sure this points to something of your choice inside the callback function. So you would do something like this:
....
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}, this); // <--- notice we give forEach two arguments now,
// the callback function _and_ a "thisArg" value
I'm not completely sure the above works in all browsers though, so here's another way to solve your issue:
... or use a temporary "self" variable
We will make a temporary variable equal to this. People often call such a variable self, but you can name it anything you want. This is important: it's only the this keyword that Javascript will treat differently inside the callback function:
....
var self = this; //<--- we basically give `this` an alternative
// name to use inside the callback.
args.props.forEach(function(prop) {
if(prop.name == 'label'){
self.label = prop.value; //<--- replaced `this` with `self`
alert("found label " + self.label); //<--- here as well
}
});
... or use hitch() from dojo/_base/lang
Some people don't like the self solution, perhaps because they like to consistently use this to refer to the owning object. Many frameworks therefore have a "bind" function, that makes sure a function is always called in a particular scope. In dojo's case, the function is called hitch. Here's how you can use it:
require([....., "dojo/_base/lang"], function(....., DojoLang) {
....
args.props.forEach(DojoLang.hitch(this, function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}));
... or use Javascript's own bind()
Dojo and pretty much every other framework out there has a hitch() function. Because it's such a commonly used concept in Javascript, the new Javascript standard actually introduces it's own variant, Function.prototype.bind(). You can use it like this:
....
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}.bind(this));
That was a very long answer for a pretty small thing, I hope it makes some sense!

Related

Event only firing as inline JS statement

I have the following code in a Nuxtjs app in SSR mode.
<Component
:is="author.linkUrl ? 'a' : 'div'"
v-bind="!author.linkUrl && { href: author.linkUrl, target: '_blank' }"
#click="author.linkUrl ? handleAnalytics() : null"
>
The click event in case it's an a tag, will only fire if it's written as handleAnalytics(), but handleAnalytics will not work.
Don't get me wrong the code is working, but I don't understand why.
With classical event binding (#click="handleAnalytics), Vue will auto bind it for you because it sees it's a function.
But when provided a ternary condition, it's not auto binded but wrapped into a anonymous function instead. So you have to call it with parenthesis otherwise you're just returning the function without executing it.
To be clearer, you can write it this way: #click="() => author.linkUrl ? handleAnalytics() : null"
Note: when having a dynamic tag component, I'd suggest to use the render function instead.
This is an advanced technique, but this way you won't bind things to an element that doesn't need it (without having the kind of hack to return null).
Example:
export default {
props: {
author: { type: Object, required: true },
},
render (h: CreateElement) {
const renderLink = () => {
return h('a', {
attrs: {
href: author.linkUrl,
target: '_blank',
},
on: {
click: this.handleAnalytics
},
)
}
const renderDiv = () => {
return h('div')
}
return this.author.linkUrl ? renderLink() : renderDiv()
}
}
Documention: Vue2, Vue3
In javascript functions are a reference to an object. Just like in any other language you need to store this reference in memory.
Here are a few examples that might help you understand on why its not working:
function handleAnalytics() { return 'bar' };
const resultFromFunction = handleAnalytics();
const referenceFn = handleAnalytics;
resultFromFunction will have bar as it's value, while referenceFn will have the reference to the function handleAnalytics allowing you to do things like:
if (someCondition) {
referenceFn();
}
A more practical example:
function callEuropeanUnionServers() { ... }
function callAmericanServers() { ... }
// Where would the user like for his data to be stored
const callAPI = user.preferesDataIn === 'europe'
? callEuropeanUnionServers
: callEuropeanUnionServers;
// do some logic
// ...
// In this state you won't care which servers the data is stored.
// You will only care that you need to make a request to store the user data.
callAPI();
In your example what happens is that you are doing:
#click="author.linkUrl ? handleAnalytics() : null"
What happens in pseudo code is:
Check the author has a linkUrl
If yes, then EXECUTE handleAnalytics first and then the result of it pass to handler #click
If not, simply pass null
Why it works when you use handleAnalytics and not handleAnalytics()?
Check the author has a linkUrl
If yes, then pass the REFERENCE handleAnalytics to handler #click
If not, simply pass null
Summary
When using handleAnalytics you are passing a reference to #click. When using handleAnalytics() you are passing the result returned from handleAnalytics to #click handler.

vue function not returning dynamic property instead showing initial value

I am trying to make a progress bar the progress bar works fine but its not changing text within html and keeps static 0%. N.B I am pasting here only relevant codes to avoid a large page of code.
<div class="progressTopBar"><div class="inner-progressBar" :style="{width: this.ProgressBar }">
#{{ getProgressBar() }}
</div></div>
//property
data: function () {
return {
ProgressBar:"0%",
}
}
//function on change to upload and make progress
fileSelected(e) {
let fd = new FormData();
fd.append('fileInput', $("#file")[0].files[0], $("#file")[0].files[0].name);
axios.post("/admin/chatFileUpload", fd, {
onUploadProgress: function (uploadEvent) {
this.ProgressBar = Math.round((uploadEvent.loaded / uploadEvent.total)*100) + '%';
$(".inner-progressBar").css("width", this.ProgressBar);
}
});
},
//getting progress bar value in text which only returns preset value
getProgressBar() {
return this.ProgressBar;
},
You need to make getProgressBar() a computed property instead of a method.
computed: {
getProgressBar() {
return this.progressBar;
}
}
Also, you should use camel case or snake case for your variables.
The problem is the scoping of this in the code below:
onUploadProgress: function (uploadEvent) {
this.ProgressBar = Math.round((uploadEvent.loaded / uploadEvent.total)*100) + '%';
Because this is a new function it has its own this value, it does not use the this value from the surrounding code.
The simplest way to fix this is to use an arrow function:
onUploadProgress: (uploadEvent) => {
this.ProgressBar = Math.round((uploadEvent.loaded / uploadEvent.total)*100) + '%';
An arrow function retains the this value from the surrounding scope.
I also suggest getting rid of the jQuery line starting $(".inner-progressBar"), that shouldn't be necessary once you fix the this problem as it will be handled by the template instead.
Further, it's unclear why you have a getProgressBar method at all. If it is just going to return ProgressBar then you can use that directly within your template without the need for a method.

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!

Polymer2 Shadow dom select child element

I am working on a polymer2 shadow dom template project need to select children elements from parent elements. I found this article introduces a way to select child shadow dom elements that like this:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
However, when I tried in my polymer2 project, like this:
//First: works!!
document.querySelector('container')
.shadowRoot.querySelector('app-grid')
.shadowRoot.querySelector('#apps');
//Second: Doesn't work!// got null
document.querySelector('container::shadow app-grid::shadow #apps')
// Thrird: document.querySelector('* /deep/ #apps') // Doesn't work, got null
I really need the second way or the third, which to put selectors in (), but both couldn't work. Does anyone know why the second one doesn't work? Thank you so much!
::shadow and /deep/ has never(?) worked in Firefox, and is depraved in Chrome 63 and later.
Source
Eric Biedelman has written a nice querySelector method for finding all custom elements on a page using shadow DOM. I wouldn't use it myself, but I have implemented it so I can "querySelect" custom elements in the console. Here is his modified code:
// EXAMPLES
// findCustomElement('app-grid') // Returns app-grid element
// findCustomElements('dom-if') // Returns an array of dom-if elements (if there are several ones)
// findCustomElement('app-grid').props // Returns properties of the app-grid element
function findCustomElement(customElementName) {
const allCustomElements = [];
customElementName = (customElementName) ? customElementName.toLowerCase() : customElementName;
function isCustomElement(el) {
const isAttr = el.getAttribute('is');
// Check for <super-button> and <button is="super-button">.
return el.localName.includes('-') || isAttr && isAttr.includes('-');
}
function findAllCustomElements(nodes) {
for (let i = 0, el; el = nodes[i]; ++i) {
if (isCustomElement(el)) {
el.props = el.__data__ || el.__data || "Doesn't have any properties";
if (customElementName && customElementName === el.tagName.toLowerCase()) {
allCustomElements.push(el);
} else if (!customElementName) {
allCustomElements.push(el);
}
}
// If the element has shadow DOM, dig deeper.
if (el.shadowRoot) {
findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
}
}
}
findAllCustomElements(document.querySelectorAll('*'));
if (allCustomElements.length < 2) {
return allCustomElements[0] || customElementName + " not found";
} else if (customElementName) {
allCustomElements.props = "Several elements found of type " + customElementName;
}
return allCustomElements;
}
Remove the if (isCustomElement(el)) { statement, and you can querySelect whatever element and get an array of it if several of them exists. You can change findAllCustomElements to implement a smarter querySelect using the recursive loop on shadowDoom as base. Again, I wouldn't use this myself – and instead pass on variables from parent element(s) to children where the children have observers that activates specific behaviors – but I wanted to give you a general implementation of a fallback if nothing else works.
The problem with your question is that you don't give any specifics about WHY you want to select the children in the first place.

How to call VIEW functions within a XTemplate (itemTpl)

There are a couple of questions about this that works, but don't in my scenario.
I want to invoke a function defined in my superclass view, instead of invoking a function in the itemTpl itself.
I´ve something like this in the itemTpl:
'<span class="x-button-label" style="" id="ext-element-115">{[this.getTranslation("textcodestr", "Text by default")]}</span>',
And then, I've this function in the itemTpl config.
getTranslation: function (textCode, defaultText) {
var store = Ext.getStore('TranslationsStore');
var index = store.findExact('TextCode', textCode)
if (index > -1) {
return store.getAt(index).data.TextTranslated;
}
return defaultText;
}
It works, but I want to move this function to the superclass of the view, and be able to invoke the function within the template, instead of copy and paste in all the application templates.
Of course that use a hyper-long line that do what is in the function is not ideal.
Is there a way to do this ?
Thanks!
Milton
I managed to solve it by using a singleton helper, which I can invoke from the itemTpl.
Ext.define('MyApp.util.Shared', {
singleton : true,
getTranslation: function (textCode, defaultText) {
var store = Ext.getStore('TranslationsStore');
var index = store.findExact('TextCode', textCode)
if (index > -1) {
return store.getAt(index).data.TextTranslated;
}
return defaultText;
}});
Then, in the itemTpl
'<span class="x-button-label" style="" id="ext-element-115">{[MyApp.util.Shared.getTranslation("textcodestr", "Text by default")]}</span>',
HTH!
Milton.