WebComponents - Attribute Changed - javascript

From the link qr-code.js I have the code below.
Then I don't understand, on the highlighted line (60), what means the suffix: "Changed"?
attributeChangedCallback: {
value: function (attrName, oldVal, newVal) {
var fn = this[attrName+'Changed'];
if (fn && typeof fn === 'function') {
fn.call(this, oldVal, newVal);
}
this.generate();
}
Also I don't understand the usage of:
this[attrName+'Changed']
Could you explain me this?, I don't find any clear explanation about this on Google. Thanks.
Below is the full code:
'use strict';
(function(definition) {
if (typeof define === 'function' && define.amd) {
define(['QRCode'], definition);
} else if (typeof module === 'object' && module.exports) {
var QRCode = require('qrjs');
module.exports = definition(QRCode);
} else {
definition(window.QRCode);
}
})(function(QRCode) {
//
// Prototype
//
var proto = Object.create(HTMLElement.prototype, {
//
// Attributes
//
attrs: {
value: {
data: null,
format: 'png',
modulesize: 5,
margin: 4
}
},
defineAttributes: {
value: function () {
var attrs = Object.keys(this.attrs),
attr;
for (var i=0; i<attrs.length; i++) {
attr = attrs[i];
(function (attr) {
Object.defineProperty(this, attr, {
get: function () {
var value = this.getAttribute(attr);
return value === null ? this.attrs[attr] : value;
},
set: function (value) {
this.setAttribute(attr, value);
}
});
}.bind(this))(attr);
}
}
},
//
// LifeCycle Callbacks
//
createdCallback: {
value: function () {
this.createShadowRoot();
this.defineAttributes();
this.generate();
}
},
attributeChangedCallback: {
value: function (attrName, oldVal, newVal) {
var fn = this[attrName+'Changed'];
if (fn && typeof fn === 'function') {
fn.call(this, oldVal, newVal);
}
this.generate();
}
},
//
// Methods
//
getOptions: {
value: function () {
var modulesize = this.modulesize,
margin = this.margin;
return {
modulesize: modulesize !== null ? parseInt(modulesize) : modulesize,
margin: margin !== null ? parseInt(margin) : margin
};
}
},
generate: {
value: function () {
if (this.data !== null) {
if (this.format === 'png') {
this.generatePNG();
}
else if (this.format === 'html') {
this.generateHTML();
}
else if (this.format === 'svg') {
this.generateSVG();
}
else {
this.shadowRoot.innerHTML = '<div>qr-code: '+ this.format +' not supported!</div>'
}
}
else {
this.shadowRoot.innerHTML = '<div>qr-code: no data!</div>'
}
}
},
generatePNG: {
value: function () {
try {
var img = document.createElement('img');
img.src = QRCode.generatePNG(this.data, this.getOptions());
this.clear();
this.shadowRoot.appendChild(img);
}
catch (e) {
this.shadowRoot.innerHTML = '<div>qr-code: no canvas support!</div>'
}
}
},
generateHTML: {
value: function () {
var div = QRCode.generateHTML(this.data, this.getOptions());
this.clear();
this.shadowRoot.appendChild(div);
}
},
generateSVG: {
value: function () {
var div = QRCode.generateSVG(this.data, this.getOptions());
this.clear();
this.shadowRoot.appendChild(div);
}
},
clear: {
value: function () {
while (this.shadowRoot.lastChild) {
this.shadowRoot.removeChild(this.shadowRoot.lastChild);
}
}
}
});
//
// Register
//
document.registerElement('qr-code', {
prototype: proto
});
});

As #Jhecht suggested, it's a combination of the name of a attribute and the suffix "Changed" in order to create generic method names.
For example if the <qr-code> element has an attribute "foo" that is added, updated or removed, then the callback will define the fn variable to this["fooChanged"], which is equivalent to this.fooChanged.
If this method exists, it will be invoked by fn.call().
However I see nowhere in the code you posted such method signature attached to the custom element prototype, so it's useless code until further notice.

Related

Catch Bound Event In IE

Get Object function name from event list on IE works fine in Chrome btw
Example
var foo = {
fookeydown:function(e){
e.which;
... do something
}
}
$(document).on("keydown",foo.fookeydown)
$._data(document,"events").keydown[0].handler.name // return me fookeydown in Chrome
but ie is nut
You are trying to access a function's property function.name, which is not defined for IE. You could try the following implementation to define it (Notice the function name given to the function in foo):
if (!(function f() {}).name) {
Object.defineProperty(Function.prototype, 'name', {
get: function() {
var name = (this.toString().match(/^function\s*([^\s(]+)/) || [])[1];
Object.defineProperty(this, 'name', {
value: name
});
return name;
}
});
}
var foo = {
fookeydown: function fookeydown(e) {
console.log(e.which, 'keydown');
console.log($._data(document, "events").keydown[0].handler.name);
}
};
$(document).on("keydown", foo.fookeydown);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Alternative, searching in foo.fooProp:
var foo = {
fooProp: {
foofookeydown: function(e) {
console.log(e.which, 'keydown');
console.log($._data(document, "events").keydown[0].handler.name);
},
init: function() {
$(document).on("keydown", this.foofookeydown);
},
},
init: function() {
this.fooProp.init()
}
};
if (!(function f() {}).name) {
Object.defineProperty(Function.prototype, 'name', {
get: function() {
var name = '';
var values = Object.keys(foo.fooProp).map(function(e) {
return foo.fooProp[e]
});
if (values.length > 0) {
if (values.indexOf(this) > -1)
name = Object.keys(foo.fooProp)[values.indexOf(this)];
}
Object.defineProperty(this, 'name', {
value: name
});
return name;
}
});
}
foo.init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

How to print out all the keys and subkeys of an object

JSFiddle version with spacing: http://jsfiddle.net/eopth925/1/
var obj = {
test: {
test2: "test",
test3: {
base: {
base: {
check: function() {
return this;
},
va: function() {
return "value";
},
do: "this is dummy"
}
}
},
test4: {
test5: function() {
var k = 2;
k++;
return k;
},
test6: {
test7: {
test8: "test8",
test9: {
test10: "test10",
test11: function() {
return window.jQuery;
},
test12: "test12",
test13: function() {
return window.ga;
}
}
}
}
}
},
};
var output = "";
var addbreak = "\n";
var RunFunc = {
propertypri: function(a) {
Object.keys(a).forEach(function(e) {
if (typeof a[e] === 'object') {
output += `Main Key=${e}`;
RunFunc.propertyobj(e, a[e]);
}
else {
output += `Main Key=${e} value=${a[e]}` + addbreak;
}
});
console.log(output);
},
propertyobj: function(key, keyObj) {
Object.keys(keyObj).forEach(function(e) {
if (typeof keyObj[e] === 'object') {
output += ` Subkey=${e}`;
RunFunc.propertyobj(e, keyObj[e]);
}
else {
output += ` Subkey=${e} value=${keyObj[e]}` + addbreak;
}
});
}
};
RunFunc.propertypri(obj);
The way it's outputting is not view friendly. I would like to add proper indentation in the output. I am sure there is a more efficient way of doing what I am trying to accomplish.
How can I update my script so it displays like this:
Main Key=test
Subkey=test2 value=test
Subkey=test3
Subkey=base
Subkey=base
Subkey=check value=function() {
return this;
}
Subkey=va value=function() {
return "value";
}
Subkey=do value=this is dummy
Subkey=test4
Subkey=test5 value=function() {
var k = 2;
k++;
return k;
}
Subkey=test6
Subkey=test7
Subkey=test8 value=test8
Subkey=test9
Subkey=test10 value=test10
Subkey=test11 value=function() {
return window.jQuery;
}
...
Why not just use JSON.stringify?
console.log(JSON.stringify(foo, (key, val) => (typeof val === 'function' ? '' + val : val), 2))
const yourObject = { /* object properties here */ }
function printKeys(obj) {
if (typeof obj === 'object') {
const keys = Object.keys(obj)
for (const key of keys) {
printKeys(key)
}
} else {
console.log(obj)
}
}

Loop through an Object to check the functions that return true

I have an object like this one:
var BrowserDetect = {
uniqueProps: [],
browserUID: '',
browserFonts: '',
isIPhonePad: function() {
return navigator.userAgent.match(/iPhone|iPod/i);
},
isDesktop: function() {
return !navigator.userAgent.match(/iPhone|iPad|android/i);
},
isAndroid: function() {
return navigator.userAgent.match(/android/i);
},
isFirefox: function() {
return navigator.userAgent.match(/firefox/i);
},
isIOS7: function() {
return navigator.userAgent.match(/.*CPU.*OS 7_\d/i);
},
isChromeCrios: function() {
return navigator.userAgent.match(/chrome|crios/i);
},
isIPad: function() {
return navigator.userAgent.match(/iPad/i);
}
}
(FYI: There are more functions inside the object)
So I want to go through "BrowserDetect" and check which of those functions inside it return "true" and get the function's name too.
What's the easy way to achieve that? I tried to use the jquery $.each, but without success.
Use Object.keys() with Array#filter to iterate the object and return all truthy function names:
Object.keys(BrowserDetect).filter(function(key) {
var f = BrowserDetect[key];
return typeof f === 'function' && f();
});
var BrowserDetect = {
uniqueProps: [],
browserUID: '',
browserFonts: '',
isIPhonePad: function() {
return navigator.userAgent.match(/iPhone|iPod/i);
},
isDesktop: function() {
return !navigator.userAgent.match(/iPhone|iPad|android/i);
},
isAndroid: function() {
return navigator.userAgent.match(/android/i);
},
isFirefox: function() {
return navigator.userAgent.match(/firefox/i);
},
isIOS7: function() {
return navigator.userAgent.match(/.*CPU.*OS 7_\d/i);
},
isChromeCrios: function() {
return navigator.userAgent.match(/chrome|crios/i);
},
isIPad: function() {
return navigator.userAgent.match(/iPad/i);
}
};
var result = Object.keys(BrowserDetect).filter(function(key) {
var f = BrowserDetect[key];
return typeof f === 'function' && f();
});
console.log(result);
I implemented a non-functional solution in pure js. It's fairly straightforward once you consider that a property can be a function as well. Once you use call() method on the property it will run the underlying function.
var BrowserDetect = {
uniqueProps: [],
browserUID: '',
browserFonts: '',
isIPhonePad: function() {
return navigator.userAgent.match(/iPhone|iPod/i);
},
isDesktop: function() {
return !navigator.userAgent.match(/iPhone|iPad|android/i);
},
isAndroid: function() {
return navigator.userAgent.match(/android/i);
},
isFirefox: function() {
return navigator.userAgent.match(/firefox/i);
},
isIOS7: function() {
return navigator.userAgent.match(/.*CPU.*OS 7_\d/i);
},
isChromeCrios: function() {
return navigator.userAgent.match(/chrome|crios/i);
},
isIPad: function() {
return navigator.userAgent.match(/iPad/i);
}
}
var functions = [];
for(var prop in BrowserDetect){
if(typeof(BrowserDetect[prop])=="function" && BrowserDetect[prop].call()){
functions.push(prop);
}
}
console.log(functions);
You can use common for ... in loop
for (var functionName in BrowserDetect) {
if (!BrowserDetect.hasOwnProperty(functionName)
|| typeof BrowserDetect[functionName] !== "function") continue
if (BrowserDetect[functionName]())
return functionName
}
or Object.keys
Object.keys(BrowserDetect).reduce(
(current, fnName) => typeof BrowserDetect[fnName] === "function" && BrowserDetect[fnName]() ? fnName : current )

Custom Contexmenu for different node in jstree angular directive

In my project I have tree based on jstee angular directive. I build this tree with help 2 api requests. This requests provide data for folder and end point child node (filters). Also I created custom context menu, with some functions for different types of node. In this context menu I defined variable check and with help this variable I set restrictions for operations with node. The full code of context menu looks like:
$scope.contextMenu = {
"create": {
"label": "Create Folder",
"icon": "ico/folder.png",
"action": function (data) {
var inst = $.jstree.reference(data.reference),
obj = inst.get_node(data.reference),
id = obj['original']['id'];
var check = obj['original']['folderId'];
if (typeof check == "undefined") {
obj = {
text: prompt("put folder name"),
parent: id
};
eventService.createFolder(obj);
setTimeout(function () {
getTreeData();
$scope.load();
}, 100);
}
else {
alert("This function is not available");
return false
}
// inst.refresh(true)
}
},
"rename": {
"label": "Rename Folder",
"icon": "ico/folder.png",
"action": function (data) {
var inst = $.jstree.reference(data.reference),
obj = inst.get_node(data.reference),
check = obj['original']['folderId'];
if (typeof check == "undefined") {
var rename = {
id: obj.id,
text: prompt("put your name")
};
eventService.modifyFolder(rename);
inst.rename_node(obj, rename.text);
}
else {
alert("This function is not available");
return false
}
}
},
"delete": {
"label": "Delete Folder",
"icon": "ico/folder.png",
"action": function (data) {
var inst = $.jstree.reference(data.reference),
obj = inst.get_node(data.reference),
node = inst.get_selected(),
check = obj['original']['folderId'];
if (typeof check == "undefined") {
if (!node.length) {
return false;
}
eventService.deleteFolder(node);
inst.delete_node(node);
}
else {
alert("This function is not available");
return false
}
}
},
"store": {
"label": "Store Filter",
"icon": "ico/file.png",
"action": function (data) {
$scope.saveStateString();
var inst = $.jstree.reference(data.reference),
obj = inst.get_node(data.reference),
id = obj['original']['id'],
check = obj['original']['folderId'];
if (typeof check == "undefined") {
obj = {
body: $scope.state,
folderId: id,
text: prompt("put filter name")
};
// console.log(obj.body);
setTimeout(function () {
eventService.storeFilter(obj);
}, 1000);
setTimeout(function () {
getTreeData();
$scope.load();
alert("click to public filters to refresh the tree")
}, 1500)
} else {
alert("This function is not available");
return false
}
}
},
"remove": {
"label": "Remove Filter",
"icon": "ico/file.png",
"action": function (data) {
var inst = $.jstree.reference(data.reference),
node = inst.get_selected(),
obj = inst.get_node(data.reference),
check = obj['original']['folderId'];
if (typeof check == "undefined") {
alert("This function is not available");
return false
} else {
if (!node.length) {
return false;
}
eventService.deleteFilter(node);
inst.delete_node(node);
}
}
}
};
Now I'm trying to split this context menu by type of node, that for each node I could see different context menu, I found some solutions here and it says:
$('#jstree').jstree({
'contextmenu' : {
'items' : customMenu
},
'plugins' : ['contextmenu', 'types'],
'types' : {
'#' : { /* options */ },
'level_1' : { /* options */ },
'level_2' : { /* options */ }
// etc...
}
});
function customMenu(node)
{
var items = {
'item1' : {
'label' : 'item1',
'action' : function () { /* action */ }
},
'item2' : {
'label' : 'item2',
'action' : function () { /* action */ }
}
}
if (node.type === 'level_1') {
delete items.item2;
} else if (node.type === 'level_2') {
delete items.item1;
}
return items;
}
I was trying to repeat this way in my app but always see "undefined" instead context menu.
Could anybody help me please to resolve my problem? Plunker with my code
Be sure you are using working jstree directive. I replaced mistakes in your directive
if (config.plugins.indexOf('contextmenu') >= 0) {
if (a.treeContextmenu) {
config.contextmenu = s[a.treeContextmenu];
}
}
// if (config.plugins.indexOf('contextmenu') >= 0) {
// if (a.treeContextmenu) {
// config.contextmenu = config.contextmenu || {};
//
// if (a.treeContextmenuaction != undefined) {
// config.contextmenu.items = function(e) {
// return s.$eval(a.treeContextmenuaction)(e);
// }
// } else {
// config.contextmenu.items = function() {
// return s[a.treeContextmenu];
// }
// }
// }
// }
Your code should look like
$scope.contextMenu = {
"items": function custom (node){
// your menu
}
if(something){
// do whatever
}
return items
}
This is your working plunker!

Javascript, simple extension method that allows multiple versions of extending object

I have a straightforward "extend" method set up like this:
extend: function(source) {
for (var k in source) {
if (source.hasOwnProperty(k)) {
myThing[k] = source[k];
}
}
return myThing;
}
You use it like
myThing.extend({
newObj: {
myFunc: function () { console.log('things'); }
}
});
and it works great.
However, I would love to add the ability to have some other piece of code call this LATER:
myThing.extend({
newObj: {
mySecondFunc: function () { console.log('things'); }
}
});
and I should be able to call both myThing.newObj.myFunc() AND myThing.newObj.mySecondFunc().
I tried changing it to this:
for (var k in source) {
if (source.hasOwnProperty(k)) {
if (mtUtils.hasOwnProperty(k)) {
for (var t in k) {
mtUtils[k][t] = source[k][t];
}
} else {
mtUtils[k] = source[k];
}
}
}
but that doesn't seem to work.
function extend(dest, source) {
for (var k in source) {
if (source.hasOwnProperty(k)) {
var value = source[k];
if (dest.hasOwnProperty(k) && typeof dest[k] === "object" && typeof value === "object") {
extend(dest[k], value);
} else {
dest[k] = value;
}
}
}
return dest;
}
var myThing = {};
extend(myThing, {
newObj: {
myFunc: function() {
console.log('things');
}
}
});
extend(myThing, {
newObj: {
mySecondFunc: function() {
console.log('things');
}
}
});
myThing;
/*
Object
newObj: Object
myFunc: function () { console.log('things'); }
mySecondFunc: function () { console.log('things'); }
__proto__: Object
__proto__: Object
*/
This should fix your problem, but why not implement a recursive version of extend?
for (var k in source) {
if (source.hasOwnProperty(k)) {
if (mtUtils.hasOwnProperty(k)) {
for (var t in source[k]) {
mtUtils[k][t] = source[k][t];
}
} else {
mtUtils[k] = source[k];
}
}
}

Categories

Resources