I'm creting a jquery context menu, using https://swisnl.github.io/jQuery-contextMenu/ .
I've sucessfully done the creation part of the submenu.
I had to use build, in order to have some data that is only available on runtime.
This data appears on a submenu, and I need to have a title on each of those submenu items, as well as click funtion on each of them.
I cant seem to make it work, both the title and the function on those submenu items.
Here is my code:
$.contextMenu({
selector: '.gridRelatorioCursorMorada',
build: function ($triggerElement, e) {
var coords = $triggerElement[0].attributes['data-coord'].nodeValue;
var coordsArray = coords.split(',');
return {
callback: function (key) {
if (key === 'get') {
getdata();
}
},
items: {
get: {
name: "Get data"
},
see: {
name: "See data",
items: {
normal: { name: coords },
graus: { name: dd2dms(coordsArray[0], coordsArray[1]) },
siresp: { name: decimalToSIRESPCoordinates(coordsArray[0], coordsArray[1]) }
}
}
}
};
}
});
Since the events part of contextMenu doesnt work with build, I dont know what else to do.
I've also added the following code:
$(document).on('contextmenu', function () {
$('.context-menu-submenu > ul > li').attr('title', 'tituro');
});
But it also doesnt work.
My mistake.
Event does work on build.
This got me to get the title on each of the submenu items.
The click function I got it working with the callback function.
Scenario:
I’m developing a Vue scroll component that wraps around a dynamic number of HTML sections and then dynamically builds out vertical page navigation allowing the user to scroll or jump to page locations onScroll.
Detail:
a. In my example my scroll component wraps 3 sections. All section id’s start with "js-page-section-{{index}}"
b. The objective is to get the list of section nodes (above) and then dynamically build out vertical page (nav) navigation based on the n number of nodes found in the query matching selector criteria. Therefore, three sections will result in three page section navigation items. All side navigation start with “js-side-nav-{{index}}>".
c. Once the side navigation is rendered I need to query all the navigation nodes in order to control classes, heights, display, opacity, etc. i.e document.querySelectorAll('*[id^="js-side-nav"]');
EDIT
Based on some research here are the options for my problem. Again my problem being 3 phase DOM state management i.e. STEP 1. Read all nodes equal to x, then STEP 2. Build Side Nav scroll based on n number of nodes in document, and then STEP 3. Read all nav nodes to sync with scroll of document nodes:
Create some sort of event system is $emit() && $on. In my opinion this gets messy very quickly and feels like a poor solution. I found myself quickly jumping to $root
Vuex. but that feels like an overkill
sync. Works but really that is for parent child property state management but that again requires $emit() && $on.
Promise. based service class. This seems like the right solution, but frankly it became a bit of pain managing multiple promises.
I attempted to use Vue $ref but frankly it seems better for managing state rather than multi stage DOM manipulation where a observer event approach is better.
The solution that seems to work is Vues $nextTick(). which seems to be similar to AngularJS $digest. In essence it is a . setTimeout(). type approach just pausing for next digest cycle. That said there is the scenario where the tick doesn’t sync the time requires so I built a throttle method. Below is the code update for what is worth.
The refactored watch with nextTick()
watch: {
'page.sections': {
handler(nodeList, oldNodeList){
if (this.isNodeList(nodeList) && _.size(nodeList) && this.sideNavActive) {
return this.$nextTick(this.sideNavInit);
}
},
deep: true
},
},
The REFACTORED Vue component
<template>
<div v-scroll="handleScroll">
<nav class="nav__wrapper" id="navbar-example">
<ul class="nav">
<li role="presentation"
:id="sideNavPrefix + '-' + (index + 1)"
v-for="(item, key,index) in page.sections">
<a :href="'#' + getAttribute(item,'id')">
<p class="nav__counter" v-text="('0' + (index + 1))"></p>
<h3 class="nav__title" v-text="getAttribute(item,'data-title')"></h3>
<p class="nav__body" v-text="getAttribute(item,'data-body')"></p>
</a>
</li>
</ul>
</nav>
<slot></slot>
</div>
</template>
<script>
import ScrollPageService from '../services/ScrollPageService.js';
const _S = "section", _N = "sidenavs";
export default {
name: "ScrollSection",
props: {
nodeId: {
type: String,
required: true
},
sideNavActive: {
type: Boolean,
default: true,
required: false
},
sideNavPrefix: {
type: String,
default: "js-side-nav",
required: false
},
sideNavClass: {
type: String,
default: "active",
required: false
},
sectionClass: {
type: String,
default: "inview",
required: false
}
},
directives: {
scroll: {
inserted: function (el, binding, vnode) {
let f = function(evt) {
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f);
}
};
window.addEventListener('scroll', f);
}
},
},
data: function () {
return {
scrollService: {},
page: {
sections: {},
sidenavs: {}
}
}
},
methods: {
getAttribute: function(element, key) {
return element.getAttribute(key);
},
updateViewPort: function() {
if (this.scrollService.isInCurrent(window.scrollY)) return;
[this.page.sections, this.page.sidenavs] = this.scrollService.updateNodeList(window.scrollY);
},
handleScroll: function(evt, el) {
if ( !(this.isScrollInstance()) ) {
return this.$nextTick(this.inViewportInit);
}
this.updateViewPort();
},
getNodeList: function(key) {
this.page[key] = this.scrollService.getNodeList(key);
},
isScrollInstance: function() {
return this.scrollService instanceof ScrollPageService;
},
sideNavInit: function() {
if (this.isScrollInstance() && this.scrollService.navInit(this.sideNavPrefix, this.sideNavClass)) this.getNodeList(_N);
},
inViewportInit: function() {
if (!(this.isScrollInstance()) && ((this.scrollService = new ScrollPageService(this.nodeId, this.sectionClass)) instanceof ScrollPageService)) this.getNodeList(_S);
},
isNodeList: function(nodes) {
return NodeList.prototype.isPrototypeOf(nodes);
},
},
watch: {
'page.sections': {
handler(nodeList, oldNodeList){
if (this.isNodeList(nodeList) && _.size(nodeList) && this.sideNavActive) {
return this.$nextTick(this.sideNavInit);
}
},
deep: true
},
},
mounted() {
return this.$nextTick(this.inViewportInit);
},
}
</script>
END EDIT
ORIGINAL POST
Problem & Question:
PROBLEM:
The query of sections and render of navs work fine. However, querying the nav elements fails as the DOM has not completed the render. Therefore, I’m forced to use a setTimeout() function. Even if I use a watch I’m still forced to use timeout.
QUESTION:
Is there a promise or observer in Vue or JS I can use to check to see when the DOM has finished rendering the nav elements so that I can then read them? Example in AngularJS we might use $observe
HTML EXAMPLE
<html>
<head></head>
<body>
<scroll-section>
<div id="js-page-section-1"
data-title="One"
data-body="One Body">
</div>
<div id="js-page-section-2"
data-title="Two"
data-body="Two Body">
</div>
<div id="js-page-section-3"
data-title="Three"
data-body="THree Body">
</div>
</scroll-section>
</body>
</html>
Vue Compenent
<template>
<div v-scroll="handleScroll">
<nav class="nav__wrapper" id="navbar-example">
<ul class="nav">
<li role="presentation"
:id="[idOfSideNav(key)]"
v-for="(item, key,index) in page.sections.items">
<a :href="getId(item)">
<p class="nav__counter">{{key}}</p>
<h3 class="nav__title" v-text="item.getAttribute('data-title')"></h3>
<p class="nav__body" v-text="item.getAttribute('data-body')"></p>
</a>
</li>
</ul>
</nav>
<slot></slot>
</div>
</template>
<script>
export default {
name: "ScrollSection",
directives: {
scroll: {
inserted: function (el, binding, vnode) {
let f = function(evt) {
_.forEach(vnode.context.page.sections.items, function (elem,k) {
if (window.scrollY >= elem.offsetTop && window.scrollY <= (elem.offsetTop + elem.offsetHeight)) {
if (!vnode.context.page.sections.items[k].classList.contains("in-viewport") ) {
vnode.context.page.sections.items[k].classList.add("in-viewport");
}
if (!vnode.context.page.sidenavs.items[k].classList.contains("active") ) {
vnode.context.page.sidenavs.items[k].classList.add("active");
}
} else {
if (elem.classList.contains("in-viewport") ) {
elem.classList.remove("in-viewport");
}
vnode.context.page.sidenavs.items[k].classList.remove("active");
}
});
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f);
}
};
window.addEventListener('scroll', f);
},
},
},
data: function () {
return {
page: {
sections: {},
sidenavs: {}
}
}
},
methods: {
handleScroll: function(evt, el) {
// Remove for brevity
},
idOfSideNav: function(key) {
return "js-side-nav-" + (key+1);
},
classOfSideNav: function(key) {
if (key==="0") {return "active"}
},
elementsOfSideNav:function() {
this.page.sidenavs = document.querySelectorAll('*[id^="js-side-nav"]');
},
elementsOfSections:function() {
this.page.sections = document.querySelectorAll('*[id^="page-section"]');
},
},
watch: {
'page.sections': function (val) {
if (_.has(val,'items') && _.size(val.items)) {
var self = this;
setTimeout(function(){
self.elementsOfSideNavs();
}, 300);
}
}
},
mounted() {
this.elementsOfSections();
},
}
</script>
I hope I can help you with what I'm going to post here. A friend of mine developed a function that we use in several places, and reading your question reminded me of it.
"Is there a promise or observer in Vue or JS I can use to check to see when the DOM has finished rendering the nav elements so that I can then read them?"
I thought about this function (source), here below. It takes a function (observe) and tries to satisfy it a number of times.
I believe you can use it at some point in component creation or page initialization; I admit that I didn't understand your scenario very well. However, some points of your question immediately made me think about this functionality. "...wait for something to happen and then make something else happen."
<> Credits to #Markkop the creator of that snippet/func =)
/**
* Waits for object existence using a function to retrieve its value.
*
* #param { function() : T } getValueFunction
* #param { number } [maxTries=10] - Number of tries before the error catch.
* #param { number } [timeInterval=200] - Time interval between the requests in milis.
* #returns { Promise.<T> } Promise of the checked value.
*/
export function waitForExistence(getValueFunction, maxTries = 10, timeInterval = 200) {
return new Promise((resolve, reject) => {
let tries = 0
const interval = setInterval(() => {
tries += 1
const value = getValueFunction()
if (value) {
clearInterval(interval)
return resolve(value)
}
if (tries >= maxTries) {
clearInterval(interval)
return reject(new Error(`Could not find any value using ${tries} tentatives`))
}
}, timeInterval)
})
}
Example
function getPotatoElement () {
return window.document.querySelector('#potato-scroller')
}
function hasPotatoElement () {
return Boolean(getPotatoElement())
}
// when something load
window.document.addEventListener('load', async () => {
// we try sometimes to check if our element exists
const has = await waitForExistence(hasPotatoElement)
if (has) {
// and if it exists, we do this
doThingThatNeedPotato()
}
// or you could use a promise chain
waitForExistence(hasPotatoElement)
.then(returnFromWaitedFunction => { /* hasPotatoElement */
if (has) {
doThingThatNeedPotato(getPotatoElement())
}
})
})
It is possible to declare 2 more functions in main function like this ?
var jquery4u = {
init: function() {
jquery4u.countdown.show();
},
countdown: function() {
show: function() {
console.log('show');
},
hide: function() {
console.log('hide');
}
}
}
jquery4u.init();
and i receive the following error: Uncaught SyntaxError: Unexpected token ( on this line "show: function() {"
Remove the function from the right of the countdown (demo)
var jquery4u = {
init: function() {
jquery4u.countdown.show();
},
countdown: {
show: function() {
console.log('show');
},
hide: function() {
console.log('hide');
}
}
}
jquery4u.init();
Next time, use jsFiddle to make a demo and click the "JSHint" button.
Actually, none of this will work. Unless you make countdown an object or you treat its sub-functions as proper functions.
Why: Under countdown, you created an instance of object not a function.
var jquery4u = {
countdown: function() {
show = function() {
console.log('show');
}
hide = function() {
console.log('hide');
}
jquery4u.countdown.show();
}
}
The above code is a valid code so it is possible. Unfortunately it will not return anything.
The proper way to do this is in this format:
var jquery4u = {
countdown: {
show: function() {
console.log('show');
},
hide: function() {
console.log('hide');
}
}
}
This will work. You can try it out by calling:
jquery4u.countdown.show();
js.
Right Now:
I have a graphView containing 3 tabs A, B, and C.
For each tab A, B or C, each has a view and a coffee.js. In each view, there's a button and in each corresponding coffee.js, each contains a triggerSelf function.
As following:
for A: in its view: loadButtonA ; in its aCoffee.js: triggerA function
for B: in its view: loadButtonB ; in its bCoffee.js: triggerB function
for C: in its view: loadButtonC ; in its cCoffee.js: triggerC function
What I want to achieve:
When I click any button, I want to trigger the other 2 functions in other 2 files.
For example: When I click loadButtonA, I call triggerA, triggerB in bcoffee.js, and triggerC in cCoffee.js
Could anyone help please?
Thank you.
I would do it through the controller which (presumably) they all share.
App.ViewA = Ember.View.extend({
actions: {
triggerA: function () {
this.trigger();
}
},
trigger: function () {
/* Handle specific here */
this.get("controller").send("triggerAny", this);
}
});
App.GraphController = Ember.Controller.extend({
graphViews: [], // cache all subview instances here
actions: {
triggerAny: function (sender) {
this.get("graphViews").forEach(function (gv) {
if (gv === sender) {
return;
}
gv.trigger();
});
}
}
});
If I run a test case like this, my test results show up inside the YUI test-console widget:
YUI({debug: true}).use('test', 'event-base', 'test-console', function (Y) {
fooTests = new Y.Test.Case({
name: "foo",
testFoo: function () {
Y.assert(5 == 6);
}
});
Y.on("domready", function () {
(new Y.Test.Console({
newestOnTop: false,
style: 'block'
})).render('#log');
Y.Test.Runner.add(fooTests);
Y.Test.Runner.run();
});
});
If I run the exact same code but create another YUI instance that uses 'test' first, the tests show up in the browser javascript console (if it's open).
YUI({debug: true}).use('test', function (Y) {
});
YUI({debug: true}).use('test', 'event-base', 'test-console', function (Y) {
fooTests = new Y.Test.Case({
name: "foo",
testFoo: function () {
Y.assert(5 == 6);
}
});
Y.on("domready", function () {
(new Y.Test.Console({
newestOnTop: false,
style: 'block'
})).render('#log');
Y.Test.Runner.add(fooTests);
Y.Test.Runner.run();
});
});
Is there a way to get the results to show up in the test-console widget when 'test' is used by another YUI instance?
I found the answer here.
I had to add
logSource: Y.Global
To Test.Console's parameter object.
(new Y.Test.Console({
logSource: Y.Global,
newestOnTop: false,
style: 'block'
})).render('#log');