I have multiple lists on an asp.net webform. I need to show a different popup menu for each list - menu

My form has a Master page, i.e., cannot use a popup Form.
I used the code below to create a popup menu associated with one of the lists, but the popup appears BELOW my list items (I can only see the last two items!) and it appears no matter which list I right click on.
Any help will be appreciated.
Here is my code (modified from a web site example):
<div id="contextMenu" class="context-menu"
style="display: none">
<ul>
<li>New</li>
<li>Edit</li>
<li>View</li>
<li><a href="#">Copy
<li>Refresh</li>
</ul>
</div>
<script type="text/javascript">
document.getElementById('# <% = lstContactsClerk.ClientID %>').oncontextmenu = rightClick;
function rightClick(clickEvent) {
clickEvent.preventDefault();
// return false;
}
</script>
<script>
document.onclick = hideMenu;
document.oncontextmenu = rightClick;
function hideMenu() {
document.getElementById("contextMenu")
.style.display = "none"
}
function rightClick(e) {
e.preventDefault();
if (document.getElementById("contextMenu")
.style.display == "block")
hideMenu();
else {
var menu = document.getElementById("contextMenu")
menu.style.display = 'block';
menu.style.left = e.pageX + "px";
menu.style.top = e.pageY + "px";
}
}
</script>

Related

Is there a way to play background music using Google Apps Script?

I am creating an add-on which would ask the user to select music from a list and it would play it as background music. But previous posts show a sidebar with the user manually pressing the play button. I am wondering if there is a way to play it with Google Apps Script only. Also what would be helpful is if there was a volume property to set the volume?
My Code:
function onOpen(){
DocumentApp.getUi()
.createMenu("Background Music Add-On")
.addItem("Select Music","music")
.addItem("Set Volume","musicVol")
.addToUi();
}
//music selection
function music(){
var musicName = DocumentApp.getUi()
.prompt("Please select one of the music names:\n\nElevator Music,\nLeaf Rag.\nso on...")
switch(musicName){
case "Elevator":
//code to play music Elevator
break;
//So On
}
}
Playing music from a Playlist stored on your Google Drive
This script allows you to store mp3's on your Google Drive. It allows you to select which files you wish to listen too via a playlist. You must start the playlist the first time manually but then the rest of the selections play automatically. The script converts the mp3 files into dataURI's and loads them into the audio element. You can skip over the current selection and you can restart the playlist when it completes.
Code.gs
function onOpen() {
SpreadsheetApp.getUi().createMenu('My Music')
.addItem('Launch Music', 'launchMusicDialog')
.addItem('Create New Music List', 'createMusicList')
.addToUi();
}
function convMediaToDataUri(filename){
var filename=filename || "default.mp3";
var folder=DriveApp.getFolderById("Music Folder Id");
var files=folder.getFilesByName(filename);
var n=0;
while(files.hasNext()) {
var file=files.next();
n++;
}
if(n==1) {
var blob=file.getBlob();
var b64DataUri='data:' + blob.getContentType() + ';base64,' + Utilities.base64Encode(blob.getBytes());
Logger.log(b64DataUri)
var fObj={filename:file.getName(),uri:b64DataUri}
return fObj;
}
throw("Multiple Files with same name.");
return null;
}
function launchMusicDialog() {
var userInterface=HtmlService.createHtmlOutputFromFile('music1');
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Music');
}
function createMusicList() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName("MusicList");
var folder=DriveApp.getFolderById("Music Folder Id");
var files=folder.getFiles();
var mA=[['Item','File Name','File Type','File Id','Play List']];
sh.clearContents()
var n=1;
while(files.hasNext()) {
var file=files.next();
mA.push([n++,file.getName(),file.getMimeType(),file.getId(),'']);
}
sh.getRange(1,1,mA.length,mA[0].length).setValues(mA);
sh.getRange(2,2,sh.getLastRow()-1,sh.getLastColumn()-1).sort({column:2,ascending:true});
sh.getRange(2,5,sh.getLastRow()-1,1).insertCheckboxes();
}
function getPlaylist() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('MusicList');
var rg=sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn());
var vA=rg.getValues();
var pl=[];
for(var i=0;i<vA.length;i++) {
if(vA[i][4]) {
pl.push(vA[i][1]);
}
}
return pl;
}
music1.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<style>
label{margin:2px 10px;}
</style>
</head>
<script>
var selectionList=[];
var gVolume=0.2;
var index=0;
$(function(){
document.getElementById('msg').innerHTML="Loading Playlist";
google.script.run
.withSuccessHandler(function(pl){
selectionList=pl;
console.log(pl);
google.script.run
.withSuccessHandler(function(fObj){
$('#audio1').attr('src',fObj.uri);
var audio=document.getElementById("audio1");
audio.volume=gVolume;
audio.onended=function() {
document.getElementById('status').innerHTML='Ended...';
playnext();
}
var msg=document.getElementById('msg');
msg.innerHTML="Click play to begin playlist. Additional selections will begin automatically";
audio.onplay=function() {
document.getElementById('msg').innerHTML='Playing: ' + selectionList[index-1];
document.getElementById('status').innerHTML='Playing...';
document.getElementById('skipbtn').disabled=false;
}
audio.onvolumechange=function(){
gVolume=audio.volume;
}
})
.convMediaToDataUri(selectionList[index++]);
})
.getPlaylist();
});
function playnext() {
if(index<selectionList.length) {
document.getElementById('status').innerHTML='Loading...';
document.getElementById('msg').innerHTML='Next Selection: ' + selectionList[index];
google.script.run
.withSuccessHandler(function(fObj){
$('#audio1').attr('src',fObj.uri);
var audio=document.getElementById('audio1');
audio.volume=gVolume;
audio.play();
})
.convMediaToDataUri(selectionList[index++]);
}else{
document.getElementById('status').innerHTML='Playlist Complete';
document.getElementById('msg').innerHTML='';
document.getElementById('cntrls').innerHTML='<input type="button" value="Replay Playlist" onClick="replayPlaylist()" />';
}
}
function replayPlaylist() {
index=0;
document.getElementById('cntrls').innerHTML='';
playnext();
}
function skip() {
var audio=document.getElementById('audio1');
document.getElementById('skipbtn').disabled=true;
audio.pause();
index++;
playnext();
}
</script>
<body>
<div id="msg"></div>
<audio controls id="audio1" src=""></audio><br />
<div id="status"></div>
<div><input type="button" id="skipbtn" value="Skip" onClick="skip()" disabled /></div>
<div id="cntrls"></div>
</body>
</html>
Please read through the code. You need to add a music folder id and a couple of default.mp3's. The createMusicList() function reads your Music Folder and Loads them into a sheet named 'MusicList' with columns of "Item", "File Name", "File Type" ,"File Id", and PlayList. The last column is just a column of unchecked checkboxes for you to make your current playlist selection. Only one playlist for now, so you can enjoy building your own.
Here's what the dialog looks like:
And here's an image of my MusicList Sheet:
This is where you make your playlist selections.
Audio Properties and Methods
Apps Script Documentation
Latest Script Code
I used the answer to this question as a starting point: playing sound with google script
You would need to open a html sidebar and use an audio tag, to do this you can use the HtmlService class [1].
As a total background you can't, the sidebar must be always open to play the music. But you could still play the audio while editing the document.
To add the audio setting you can add the controls attribute to the audio tag [2]. For playing the audio automatically you can add the autoplay attibute [3].
Here is the code I implemented to achieve your goal. The code gets the selected value and uses it to change the autoplay value to true and to display the audio as well. Also, when the select element is on focus, it gets the previous selected value so later (when a new value is selected) it can be used to stop the previous audio selection and not display it anymore. For these purposes I used the onchange [4] and the onfocus [5] events.
Code.gs
var SIDEBAR_TITLE = 'Sidebar Musicbox';
function onOpen(e) {
DocumentApp.getUi()
.createMenu('Custom Menu')
.addItem('Show sidebar', 'showSidebar')
.addToUi();
}
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setTitle(SIDEBAR_TITLE);
DocumentApp.getUi().showSidebar(ui);
}
Sidebar.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div class="sidebar branding-below">
<p>
A little music for your enjoyment!
</p>
<form>
<select id="music" onchange="playSelection();" onfocus="setOldValue(this.value);">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</form>
<br>
<audio id="player0" controls style="display:none">
<source src="[WEB-URL-FOR-MP3-FILE]" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<audio id="player1" controls style="display:none">
<source src="[WEB-URL-FOR-MP3-FILE]" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<audio id="player2" controls style="display:none">
<source src="[WEB-URL-FOR-MP3-FILE]" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<br>
<div id="sidebar-status"></div>
</div>
<div class="sidebar bottom">
<span class="gray branding-text">Docs Add-on Sound Demo</span>
</div>
</body>
<script>
var previousValue;
//Function called when select onFocus
function setOldValue(e) {
previousValue = e;
}
//Function called when selected value change
function playSelection() {
//Get the value for the selected option
var selectedValue = document.getElementById("music").value;
//Latest and previous selection IDs
var player = "player" + selectedValue;
var previousPlayer = "player" + previousValue;
//Stop and don't display the previous selection of audio
document.getElementById(previousPlayer).style.display = "none";
document.getElementById(previousPlayer).autoplay = false;
document.getElementById(previousPlayer).load();
//Play and display the new selection and put the focus on it
document.getElementById(player).style.display = "block";
document.getElementById(player).autoplay = true;
document.getElementById(player).load();
document.getElementById(player).focus();
}
</script>
</html>
[1] https://developers.google.com/apps-script/guides/html/
[2] https://www.w3schools.com/tags/att_audio_controls.asp
[3] https://www.w3schools.com/tags/att_audio_autoplay.asp
[4] https://www.w3schools.com/jsref/event_onchange.asp
[5] https://www.w3schools.com/jsref/event_onfocus.asp

how to create live search with knockout?

I have project where we need to create a live search with knockout, map with some titles, when the title enter in search field other titles should disappear, I try to use different code but non of them work, and there is no error to help me figure out.
my code for html :
Filter:
<ul data-bind="foreach: locations">
<li data-bind="text: title"></li>
</ul>
</div>
And for js:
function ViewModel(){
var self =this;
this.filter = ko.observable();
this.locations = ko.observableArray([{ title:'Safa Bridge'},{ title:'Holy Mosque'},{ title:'Diamond Tower'},{ title:'Albaik Resturant'},{ title:'Zamzam'}]);
this.visibleLocations = ko.computed(function(){
return this.locations().filter(function(location){
if(!self.filter() || location.title.toLowerCase().indexOf(self.filter().toLowerCase()) !== -1)
return location;
});
},this);
}
ko.applyBindings(new ViewModel());

Vue.js - Pagination

I have the following pagination component.
If users adds remove items dynamically i.e via some ajax call, how do i ensure the correct active or disabled classes are applied on the pagination links?
For example if the user is currently on the last page which only has 1 item, if the user deletes that item, the pagination links re-render but then i lose the active disable class becuase that page no longer exists. i.e. the links should update to move the user to previous page.
<div class="comment-pager ">
<div class="panel panel-default panel-highlight no-border ">
<div class="panel-body padded-5">
<nav v-if="totalItems > pageSize">
<ul class="pagination">
<li v-bind:class="[currentPage == 1 ? disabled : '']">
<a v-on:click.prevent="previous()" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li v-bind:class="[currentPage == pages ? active : '']" v-for="page in pages" v-on:click.prevent="changePage(page)">
<a>{{ page }}</a>
</li>
<li v-bind:class="[currentPage == pages.length ? disabled : '']">
<a v-on:click.prevent="next()" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['totalItems', 'pageSize']
data: function () {
return {
currentPage: 1,
pages: [],
}
},
watch: {
totalItems: function () {
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
this.pages = [];
for (var i = 1; i <= pagesCount; i++)
this.pages.push(i);
}
},
methods: {
changePage: function (page){
this.currentPage = page;
this.$emit('pageChanged', page);
}
previous: function (){
if (this.currentPage == 1)
return;
this.currentPage--;
this.$emit('pageChanged', this.currentPage);
}
next: function () {
if (this.currentPage == this.pages.length)
return;
this.currentPage++;
this.$emit('pageChanged', this.currentPage);
}
},
}
</script>
<paginator v-bind:total-items="totalItems" v-bind:page-size="query.pageSize" v-on:pageChanged="onPageChange"></paginator>
There is no complete equivalent to ngOnChanges() in vue.
ngOnChanges() is a lifecycle hook which takes in an object that maps each changed property name to a SimpleChange object holding the current and previous property values.
If you want the lifecycle hook that gets invoked after every change in data and before re-rendering the virtual DOM then you should be using beforeUpdate() hook.
But as in ngOnChanges() you can't get the hold of which property is updated or what is it's oldvalue or newValue is.
As mklimek answered you can set up watcher on the properties you want to watch for changes.
In watcher you get what the oldValue is and what it's changed new value is
new Vue({
el:'#app',
data:{
prop1: '',
prop2: '' // property to watch changes for
},
watch:{
prop#(newValue, oldValue){
console.log(newValue);
console.log(oldValue);
}
}
});
EDIT
For your case you do not need a watcher. You can setup the pages[] property as a computed property:
computed:{
pages(){
var pageArray = [];
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
for (var i = 1; i <= pagesCount; i++)
pages.push(i);
}
return pageArray;
}
computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed in your case the props
totalItems and pageSize
Now you can use the pages computed property as normal data property
You probably want to use watch property of a Vue instance.
var vm = new Vue({
data: {
count: 1
},
watch: {
count: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
})

MathJax is rendering twice

I have a MathJax demo that can be viewed at Online Demo.
In this demo, I have some Tex markup within a div that gets rendered perfectly by MathJax.
But, if I programatically add some Tex markup to the above div by clicking Add Math Markup button followed by clicking Rerender Math Markup button, then it results in repeated rendering of previously rendered Math markup. This can be seen in following video: Math being rendered repeatedly
All I am doing when Rerender Math Markup button is clicked is calling the following method MathJax.Hub.Typset(divElement). The divElement is the div to which Tex markup was added programatically.
Demo code for my situation
<script>
function reRenderMath() {
var div = document.getElementById("mathMarkup");
//render Math for newly added Tex markup
MathJax.Hub.Typeset(div);
}
function addMath() {
var div = document.getElementById("mathMarkup");
div.innerHTML = div.innerHTML + "$$\sin^{-1}.6$$";
document.getElementById("btnRenderMath").disabled = false;
}
</script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});
</script>
<script type="text/javascript"
src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<button type="button" onclick="addMath();return false;" id="btnAddMath" >Add Math Markup</button>
<button type="button" onclick="reRenderMath();return false;" id="btnRenderMath" disabled>Rerender Math Markup</button>
<div id="mathMarkup">
$$x^2 = x +2$$
</div>
Screen shot of repeated rendering
#Sunil thanks for the answer
Summarizing:
Required script:
var MathJaxUtils = (function () {
let obj = {};
let scripts = null;
obj.render = function (element) {
scripts = new Array();
$(element).find("script[id^='MathJax-Element']").each(function () {
scripts.push({
displayElement: $(this).prev("div")[0],
scriptElement: this
});
});
//remove all html within MathJax script tags so it doesn't get typset again when Typeset method is called
$(element).find("script[id^='MathJax-Element']").remove();
//render Math using original MathJax API and in callback re-insert the MathJax script elements in their original positions
MathJax.Hub.Queue(["Typeset", MathJax.Hub, element, typeSetDone]);
};
//callback for Typeset method
function typeSetDone() {
for (var i = 0; i < scripts.length; i++) {
$(scripts[i].displayElement).after(scripts[i].scriptElement);
}
//reset scripts variable
scripts = [];
};
return obj;
}());
Basic use:
let elem = document.getElementById("mathContainer");
MathJaxUtils.render(elem);
Demo:
math-jax-test

Dijit.MenuItem and <a href=></a> link

My question is similar to that one:
Dijit Menu (bar) with link
I'm using Dijit Menu as in following listing:
<div data-dojo-type="dijit/Menu">
<div id="menuItem" data-dojo-type="dijit/MenuItem">
urlLink
</div>
</div>
But link is not working as it blocked by dojo.stopEvent in _onClick().
The question is:
How to remove dojo.stopEvent and make link inside <div id="menuItem" data-dojo-type="dijit/MenuItem"> work properly?
The issue:
I need to put inside <div id=menuItem"> some code, which has to receive onClick event.
P.S. Originally this is XPages code.
Well I fell in same problem, saw this post and the related other, but wasn't satisfied with the "onclick" solution :
it didn't work (for me) with keyboard navigation
it imposes to a add script element (onclick=...) in the declarative zone which is not what I expect for unobtrusive JavaScript
Finaly I digged further in dojo and decided to directly use the href attribute of first sub-node in the handler. My script section (derived from dijit menus tutorial) is then :
<script>
require([
"dojo/dom",
"dojo/parser",
"dojo/dom-attr",
"dojo/query",
"dijit/registry",
"dijit/WidgetSet", // for registry.byClass
"dijit/Menu",
"dijit/MenuItem",
"dijit/MenuBar",
"dijit/MenuBarItem",
"dijit/PopupMenuBarItem",
"dojo/domReady!"
], function(dom, parser, domattr, query, registry){
// a menu item selection handler
var onItemSelect = function(event){
dom.byId("lastSelected").innerHTML = this.get("label");
var achild = query("a", this.domNode)[0];
if (achild != null) {
var href = domattr.get(achild, "href");
if ((href != null) && (href != '') && (href != '#')) {
window.location.href = href;
}
}
};
parser.parse();
var setClickHandler = function(item){
item.on("click", onItemSelect);
};
registry.byClass("dijit.MenuItem").forEach(setClickHandler);
registry.byClass("dijit.MenuBarItem").forEach(setClickHandler);
});
</script>
That way I don't have to change anything in a menu of type
<ul><li>...</li></ul>
that works with JavaScript disabled, and links work fine with mouse and keyboard navigation when JavaScript is enabled. Simply don't forget the "class='claro'" in body element ....
What about this:
<div data-dojo-type="dijit/Menu">
<div id="menuItem" data-dojo-type="dijit/MenuItem"
onclick="window.location('http://url.com')">
urlLink
</div>
</div>
Working jsfiddle:
http://jsfiddle.net/KuyYX/

Resources