Related
There is a long standing issue with the v-menu component in Vuetify:
by default the popup is physically "detached" from the activator and created as a child of v-app, thus avoiding being clipped if some of the parent DOM nodes has overflow: hidden style; however, this leads to the issue that the popup behaves as "position: fixed" when the activator is inside a scrolling container - that is, it does not scroll with the activator and looks visually disconnected, just "hanging" over the page.
the Vuetify maintainers admit the fact and suggest using the "attach" prop - however, 9 times out of 10 when using "attach" the position of the popup is computed wrong.
After 2 hours of debugging I finally gave up on using the "attach" prop and decided to simply track the scrolling position of the parent container where the activator resides and take it into account when computing the position of the popup. I am sharing my solution to the issue below and hoping that it will be included in the mainstream Vuetify.
Here is the patch file which solves the above issue plus a few others. Create a folder named patches inside your project and save the patch file as patches/vuetify#2.6.4.patch. Then add a new script to the scripts group in your package.json
"scripts":
{
....
"prepare": "custompatch"
}
Then run npm -i -D custompatch (to install the patcher for your CI/CD) and npx custompatch (to actually patch Vuetify in your dev environment).
Index: \vuetify\lib\components\VDialog\VDialog.js
===================================================================
--- \vuetify\lib\components\VDialog\VDialog.js
+++ \vuetify\lib\components\VDialog\VDialog.js
## -43,17 +43,21 ##
transition: {
type: [String, Boolean],
default: 'dialog-transition'
},
- width: [String, Number]
+ width: [String, Number],
+ zIndex: {
+ type: [String, Number],
+ default: 200
+ }
},
data() {
return {
activatedBy: null,
animate: false,
animateTimeout: -1,
- stackMinZIndex: 200,
+ stackMinZIndex: this.zIndex || 200,
previousActiveElement: null
};
},
Index: \vuetify\lib\components\VMenu\VMenu.js
===================================================================
--- \vuetify\lib\components\VMenu\VMenu.js
+++ \vuetify\lib\components\VMenu\VMenu.js
## -120,10 +120,10 ##
return {
maxHeight: this.calculatedMaxHeight,
minWidth: this.calculatedMinWidth,
maxWidth: this.calculatedMaxWidth,
- top: this.calculatedTop,
- left: this.calculatedLeft,
+ top: `calc(${this.calculatedTop} - ${this.scrollY}px + ${this.originalScrollY}px)`,
+ left: `calc(${this.calculatedLeft} - ${this.scrollX}px + ${this.originalScrollX}px)`,
transformOrigin: this.origin,
zIndex: this.zIndex || this.activeZIndex
};
}
Index: \vuetify\lib\components\VSelect\VSelect.js
===================================================================
--- \vuetify\lib\components\VSelect\VSelect.js
+++ \vuetify\lib\components\VSelect\VSelect.js
## -678,12 +678,17 ##
},
onScroll() {
if (!this.isMenuActive) {
- requestAnimationFrame(() => this.getContent().scrollTop = 0);
+ requestAnimationFrame(() =>
+ {
+ const content = this.getContent();
+ if (content) content.scrollTop = 0;
+ });
} else {
if (this.lastItem > this.computedItems.length) return;
- const showMoreItems = this.getContent().scrollHeight - (this.getContent().scrollTop + this.getContent().clientHeight) < 200;
+ const content = this.getContent();
+ const showMoreItems = content ? this.getContent().scrollHeight - (this.getContent().scrollTop + this.getContent().clientHeight) < 200 : false;
if (showMoreItems) {
this.lastItem += 20;
}
Index: \vuetify\lib\components\VSlideGroup\VSlideGroup.js
===================================================================
--- \vuetify\lib\components\VSlideGroup\VSlideGroup.js
+++ \vuetify\lib\components\VSlideGroup\VSlideGroup.js
## -181,9 +181,9 ##
},
methods: {
onScroll() {
- this.$refs.wrapper.scrollLeft = 0;
+ if (this.$refs.wrapper) this.$refs.wrapper.scrollLeft = 0; // TMCDOS
},
onFocusin(e) {
if (!this.isOverflowing) return; // Focused element is likely to be the root of an item, so a
Index: \vuetify\lib\components\VTextField\VTextField.js
===================================================================
--- \vuetify\lib\components\VTextField\VTextField.js
+++ \vuetify\lib\components\VTextField\VTextField.js
## -441,8 +441,9 ##
this.$refs.input.focus();
},
onFocus(e) {
+ this.onResize();
if (!this.$refs.input) return;
const root = attachedRoot(this.$el);
if (!root) return;
Index: \vuetify\lib\directives\click-outside\index.js
===================================================================
--- \vuetify\lib\directives\click-outside\index.js
+++ \vuetify\lib\directives\click-outside\index.js
## -35,10 +35,11 ##
}
function directive(e, el, binding, vnode) {
const handler = typeof binding.value === 'function' ? binding.value : binding.value.handler;
+ const target = e.target;
el._clickOutside.lastMousedownWasOutside && checkEvent(e, el, binding) && setTimeout(() => {
- checkIsActive(e, binding) && handler && handler(e);
+ checkIsActive({...e, target}, binding) && handler && handler({...e, target});
}, 0);
}
function handleShadow(el, callback) {
Index: \vuetify\lib\directives\ripple\index.js
===================================================================
--- \vuetify\lib\directives\ripple\index.js
+++ \vuetify\lib\directives\ripple\index.js
## -119,9 +119,9 ##
el.style.position = el.dataset.previousPosition;
delete el.dataset.previousPosition;
}
- animation.parentNode && el.removeChild(animation.parentNode);
+ animation.parentNode && /* el */animation.parentNode.parentNode.removeChild(animation.parentNode);
}, 300);
}, delay);
}
Index: \vuetify\lib\mixins\detachable\index.js
===================================================================
--- \vuetify\lib\mixins\detachable\index.js
+++ \vuetify\lib\mixins\detachable\index.js
## -28,13 +28,23 ##
},
contentClass: {
type: String,
default: ''
+ },
+ scroller:
+ {
+ default: null,
+ validator: validateAttachTarget
}
},
data: () => ({
activatorNode: null,
- hasDetached: false
+ hasDetached: false,
+ scrollingNode: null,
+ scrollX: 0,
+ scrollY: 0,
+ originalScrollX: 0,
+ originalScrollY: 0
}),
watch: {
attach() {
this.hasDetached = false;
## -42,10 +52,38 ##
},
hasContent() {
this.$nextTick(this.initDetach);
+ },
+ isActive(val)
+ {
+ if (val)
+ {
+ if (typeof this.scroller === 'string') {
+ // CSS selector
+ this.scrollingNode = document.querySelector(this.scroller);
+ } else if (this.scroller && typeof this.scroller === 'object') {
+ // DOM Element
+ this.scrollingNode = this.scroller;
+ }
+ if (this.scrollingNode)
+ {
+ this.originalScrollX = this.scrollingNode.scrollLeft;
+ this.originalScrollY = this.scrollingNode.scrollTop;
+ this.scrollX = this.originalScrollX;
+ this.scrollY = this.originalScrollY;
+ this.scrollingNode.addEventListener('scroll', this.setScrollOffset, {passive: true});
+ }
+ }
+ else
+ {
+ if (this.scrollingNode)
+ {
+ this.scrollingNode.removeEventListener('scroll', this.setScrollOffset, {passive: true});
+ }
+ this.scrollingNode = null;
+ }
}
-
},
beforeMount() {
this.$nextTick(() => {
## -95,8 +133,12 ##
} else {
removeActivator(activator);
}
}
+ if (this.scrollingNode)
+ {
+ this.scrollingNode.removeEventListener('scroll', this.setScrollOffset, {passive: true});
+ }
},
methods: {
getScopeIdAttrs() {
## -132,9 +174,13 ##
}
target.appendChild(this.$refs.content);
this.hasDetached = true;
+ },
+ setScrollOffset(event)
+ {
+ this.scrollX = event.target.scrollLeft;
+ this.scrollY = event.target.scrollTop;
}
-
}
});
//# sourceMappingURL=index.js.map
\ No newline at end of file
Index: \vuetify\lib\mixins\menuable\index.js
===================================================================
--- \vuetify\lib\mixins\menuable\index.js
+++ \vuetify\lib\mixins\menuable\index.js
## -96,9 +96,9 ##
computed: {
computedLeft() {
const a = this.dimensions.activator;
const c = this.dimensions.content;
- const activatorLeft = (this.attach !== false ? a.offsetLeft : a.left) || 0;
+ const activatorLeft = (this.attach !== false ? this.getActivatorLeft() : a.left) || 0;
const minWidth = Math.max(a.width, c.width);
let left = 0;
left += activatorLeft;
if (this.left || this.$vuetify.rtl && !this.right) left -= minWidth - a.width;
## -117,9 +117,9 ##
const a = this.dimensions.activator;
const c = this.dimensions.content;
let top = 0;
if (this.top) top += a.height - c.height;
- if (this.attach !== false) top += a.offsetTop;else top += a.top + this.pageYOffset;
+ if (this.attach !== false) top += this.getActivatorTop(); else top += a.top + this.pageYOffset;
if (this.offsetY) top += this.top ? -a.height : a.height;
if (this.nudgeTop) top -= parseInt(this.nudgeTop);
if (this.nudgeBottom) top += parseInt(this.nudgeBottom);
return top;
## -130,10 +130,13 ##
},
absoluteYOffset() {
return this.pageYOffset - this.relativeYOffset;
+ },
+
+ windowContainer() {
+ return typeof this.attach === 'string' ? document.querySelector(this.attach) || document.body : typeof this.attach === 'object' ? this.attach : document.body;
}
-
},
watch: {
disabled(val) {
val && this.callDeactivate();
## -274,19 +277,19 ##
},
getInnerHeight() {
if (!this.hasWindow) return 0;
- return window.innerHeight || document.documentElement.clientHeight;
+ return this.attach !== false ? this.windowContainer.clientHeight : window.innerHeight || document.documentElement.clientHeight;
},
getOffsetLeft() {
if (!this.hasWindow) return 0;
- return window.pageXOffset || document.documentElement.scrollLeft;
+ return this.attach !== false ? this.windowContainer.scrollLeft : window.pageXOffset || document.documentElement.scrollLeft;
},
getOffsetTop() {
if (!this.hasWindow) return 0;
- return window.pageYOffset || document.documentElement.scrollTop;
+ return this.attach !== false ? this.windowContainer.scrollTop : window.pageYOffset || document.documentElement.scrollTop;
},
getRoundedBoundedClientRect(el) {
const rect = el.getBoundingClientRect();
## -368,19 +371,38 ##
this.sneakPeek(() => {
if (this.$refs.content) {
if (this.$refs.content.offsetParent) {
const offsetRect = this.getRoundedBoundedClientRect(this.$refs.content.offsetParent);
- this.relativeYOffset = window.pageYOffset + offsetRect.top;
+ this.relativeYOffset = (this.attach !== false ? this.windowContainer.scrollTop : window.pageYOffset) + offsetRect.top;
dimensions.activator.top -= this.relativeYOffset;
- dimensions.activator.left -= window.pageXOffset + offsetRect.left;
+ dimensions.activator.left -= (this.attach !== false ? this.windowContainer.scrollLeft : window.pageXOffset) + offsetRect.left;
}
dimensions.content = this.measure(this.$refs.content);
}
this.dimensions = dimensions;
});
+ },
+
+ getActivatorTop() {
+ let result = 0;
+ let elem = this.getActivator();
+ while (elem && elem !== this.windowContainer && this.windowContainer.contains(elem)) {
+ result += elem.offsetTop;
+ elem = elem.offsetParent;
+ }
+ return result;
+ },
+
+ getActivatorLeft() {
+ let result = 0;
+ let elem = this.getActivator();
+ while (elem && elem !== this.windowContainer && this.windowContainer.contains(elem)) {
+ result += elem.offsetLeft;
+ elem = elem.offsetParent;
+ }
+ return result;
}
-
}
});
//# sourceMappingURL=index.js.map
\ No newline at end of file
I have used return false statement to break the each loop in cypress. But still the loop continue..
Please find the code snippet below:
getRowActionByUrlOrDomain(Value) {
var urlName;
var flag;
cy.get(TBL_BWLIST_ROWS).each(($li, rowindex, $lis) => {
cy.wrap($li).find('td a', { timeout: 9000 }).each(($lidata, index, lis) => {
urlName = $lidata.text().trim()
rowindex = $lis.index($li)
if (urlName.localeCompare(Value.trim()) == 0) {
expect($lidata.text().trim()).to.be.equal(Value.trim())
cy.get(TBL_BWLIST_ROWS + ":nth-child(index)").find('td:nth-of-type(index) a').click()
return false;
}
})
})
}
You have two each() loops. I have added an abort variable to allow the outer loop to be exited early as well
getRowActionByUrlOrDomain(Value) {
var urlName;
var abort = false;
cy.get(TBL_BWLIST_ROWS).each(($li, rowindex, $lis) => {
cy.wrap($li).find('td a', { timeout: 9000 }).each(($lidata, index, lis) => {
urlName = $lidata.text().trim()
rowindex = $lis.index($li)
if (urlName.localeCompare(Value.trim()) == 0) {
expect($lidata.text().trim()).to.be.equal(Value.trim())
cy.get(TBL_BWLIST_ROWS + ":nth-child(index)").find('td:nth-of-type(index) a').click()
abort = true // Set this to allow the 2nd loop to be aborted as well
return false;
}
})
if (abort) return false // This breaks out of the outer loop
})
}
Grpc-node: How to edit metadata and send back edited metadata to client?
Below is currently what I have at the moment, and it seems like the metadata can be passed to the grpc-client RPC method ('greetmath'), but I can't edit the metadata in the server and send it back to the client. It can only send back metadata that was originally created.
Is there anyway I can edit the metadata in the grpc server and send that edited metadata to the grpc client?
Greeter_server.js
const path = require('path');
const PROTO_PATH = path.join(__dirname, '../proto/greet.proto');
// console.log("Proto path: ", PROTO_PATH);
const protoLoader = require('#grpc/proto-loader') //require('#grpc/proto-loader')
const grpc = require('grpc')
//grpc service definition for greet
const greetProtoDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const greetPackageDefinition = grpc.loadPackageDefinition(greetProtoDefinition).greet
function greetFunc(call, callback) {
var firstName = call.request.greeting.first_name;
var lastName = call.request.greeting.last_name;
callback(null, {result: "Hello " + firstName + " " + lastName});
}
function greetMath(call, callback) {
console.log("callback: ", call);
console.log("Meta data: ", call.metadata._internal_repr.somekey);
call.metadata._internal_repr.somekey.push('random');
var firstName = call.request.greeting.first_name;
var lastName = call.request.greeting.last_name;
let current = Number(process.hrtime.bigint());
console.log("call obj: ", call);
console.log("callback obj: ", callback);
callback(null, {result: "Hello " + firstName + " " + lastName + " current: " + current});
}
function main() {
const server = new grpc.Server()
server.addService(greetPackageDefinition.GreetService.service, {
greet: greetFunc,
greetMath: greetMath
});
server.bind("127.0.0.1:4000", grpc.ServerCredentials.createInsecure());
server.start();
console.log("Server Running at http://127.0.0.1:50051")
}
main()
greet.proto
syntax = "proto3";
package greet;
service GreetService {
//Unary API
rpc Greet (GreetRequest) returns (GreetResponse) {};
rpc GreetMath(GreetRequest) returns (GreetResponse) {};
}
message Greeting {
string first_name = 1;
string last_name = 2;
}
message GreetRequest {
Greeting greeting = 1;
}
message GreetResponse {
string result = 1;
}
greeter_client.js
const path = require('path');
const PROTO_PATH = path.join(__dirname, '../proto/greet.proto');
const protoLoader = require('#grpc/proto-loader') //require('#grpc/proto-loader')
const grpc = require('grpc')
//grpc service definition for greet
const greetProtoDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const greetPackageDefinition = grpc.loadPackageDefinition(greetProtoDefinition).greet
const client = new greetPackageDefinition.GreetService("localhost:4000",
grpc.credentials.createInsecure()
)
function callGreetings() {
var request = {
greeting: {
first_name: "Jerry",
last_name: "Tom"
}
}
client.greet(request, (error, response) => {
if(!error) {
console.log("Greeting Response: ", response.result);
} else {
console.error(error)
}
})
}
function callGreetingsLogger() {
var request = {
greeting: {
first_name: "Jerry",
last_name: "Tom"
}
}
let meta = new grpc.Metadata();
meta.add('someKey', 'someVal');
let end;
let start = Number(process.hrtime.bigint());
client.greetMath(request, meta, (error, response) => {
if(!error) {
console.log("res: ", response);
console.log("metadata: ", meta);
console.log("Greeting Response: ", response.result);
end = Number(process.hrtime.bigint());
console.log("start: ", start);
console.log("end: ", end);
console.log("end - start", (end - start)/ 1000000, "ms");
function getDateTime() {
var date = new Date();
var hour = date.getHours();
hour = (hour < 10 ? "0" : "") + hour;
var min = date.getMinutes();
min = (min < 10 ? "0" : "") + min;
var sec = date.getSeconds(); sec = (sec < 10 ? "0" : "") + sec;
var year = date.getFullYear();
var month = date.getMonth() + 1; month = (month < 10 ? "0" : "") + month;
var day = date.getDate(); day = (day < 10 ? "0" : "") + day;
return month + "/" + day + "/" + year + " | Time: " + hour + ":" + min + ":" + sec;
}
let currentTime = getDateTime();
console.log("Current time: ", currentTime);
} else {
console.error(error)
}
});
}
function main() {
callGreetings();
callGreetingsLogger();
}
main()
The question says "I can't edit the metadata in the server and send it back to the client". The code in the question edits the metadata, so I assume that that is the code you are trying to use to do that, but that is not sending the metadata because it never calls the sendMetadata method of the call. You need to call that method to send metadata back to the client.
In addition, you should not touch the _internal_repr of the metadata. The client code handles this correctly by calling meta.add(). The server code should do the same. So, instead of call.metadata._internal_repr.somekey you should write call.metadata.get('somekey') and instead of call.metadata._internal_repr.somekey.push('random') you should write call.metadata.add('somekey', 'random').
I suspect that you're trying to change the gRPC's internal metadata (call.metadata._internal_repr) and this is not permitted.
In Go(lang) for example, the only methods exposed for Metadata are e.g. GetMetadata() and there's intentionally no public mechanism to change this data.
You have some options:
Create a message type that you carry within your messages for metadata.
Consider Custom Options and Extensions if you're trying to extend the capabilities of gRPC.
I found this code on a clients website, loaded the URL which returned the attached script. This appears to me (and I could be wrong) to be some sport of dataLayer info capture to spreadsheet auto-fill script. Naturally, what concerned my was the "userInfo", "user", "password" sections of this. I'm not overly versed in this level of code so I am reaching out here! Thanks!
! function(r, e, t) {
var n = function(e) {
return "string" == typeof e
},
o = function() {
return function(e) {
for (var n = {
strictMode: !1,
key: ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:#]*)(?::([^:#]*))?)?#)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:#]+:[^:#\/]*#)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:#?]*)(?::([^:#]*))?)?#)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
}, r = n.parser[n.strictMode ? "strict" : "loose"].exec(e), o = {}, t = 14; t--;) o[n.key[t]] = r[t] || "";
o[n.q.name] = {}, o[n.key[12]].replace(n.q.parser, function(e, r, t) {
r && (r = decodeURIComponent(r), o[n.q.name][r] && o[n.q.name][r].constructor === Array ? o[n.q.name][r].push(decodeURIComponent(t)) : o[n.q.name][r] ? o[n.q.name][r] = [o[n.q.name][r], decodeURIComponent(t)] : o[n.q.name][r] = decodeURIComponent(t))
});
var s = o.host.split(".");
return o.rootDomain = 2 <= s.length ? s[s.length - 2] + "." + s[s.length - 1] : "", o.href = e, o
}(r.location.href)
},
s = function() {
if (r.rl_widget_cfg) return r.rl_widget_cfg.id;
if (r.rl_siteid) return r.rl_siteid;
var e = o().queryKey.rl_siteid;
return e || ((e = localStorage.getItem("capture_previous_site_id")) || null)
},
c = e.createElement("script");
if (r.rl_widget_cfg || r.test_mode) c.src = "https://cdn.rlets.com/capture_static/mms/capture.js";
else {
var i = function() {
var e, r, t = s();
if (t && n(t) && 32 === (t = (e = t, n(e) ? !1 !== r && e.trim ? e.trim() : e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "") : e).replace(/-/g, "")).length) return "/" + t.substr(0, 3) + "/" + t.substr(3, 3) + "/" + t.substr(6, 3) + "/" + t.substr(9) + ".js"
}();
i && (c.src = "https://cdn.rlets.com/capture_configs" + i)
}
e.head.appendChild(c)
}(window, document);
cdn.rlets.com is for a tracking pixel for reachlocal.com. It's used for marketing purposes. (I've seen it used specifically as an integration with Facebook ads.)
It's minified, so hard to say exactly what it's doing, but I don't think it's malicious (any more than marketing pixels in general are).
I am using jscodeshift to transform function calls:
foo() --> foo({uid: ... label: ...})
const newArgObj = j.objectExpression([
j.property(
'init',
j.identifier('uid'),
j.literal(getUID()),
),
j.property(
'init',
j.identifier('label'),
j.literal('bar'),
)
]);
node.arguments = [newArgObj];
...
return callExpressions.toSource({quote: 'single'});
The problem is objectExpression is always pretty-printed:
foo({
uid: 'LBL_btBeZETZ',
label: 'bar'
})
How to prevent that and get something like:
foo({uid: 'LBL_btBeZETZ', label: 'bar'})
ok, it is impossible, take a look at recast's printer.js source:
case "ObjectExpression":
case "ObjectPattern":
case "ObjectTypeAnnotation":
...
var len = 0;
fields.forEach(function(field) {
len += n[field].length;
});
var oneLine = (isTypeAnnotation && len === 1) || len === 0;
var parts = [oneLine ? "{" : "{\n"];
...
if (!oneLine) {
lines = lines.indent(options.tabWidth);
}
...
parts.push(oneLine ? "}" : "\n}");
WORKAROUND (at least in my simple case) - you can use raw string:
const rawCode = `{uid: '${getUID()}', label: 'bar' }`;
node.arguments = [rawCode];
or
node.arguments = [j.jsxText(rawCode)];
Both will give you:
foo({uid: 'LBL_btBeZETZ', label: "bar" })