Fire JavaScript code once element is added - javascript

Let's assume I want to get the following jstree in shiny (the part with the button is just to illustrate that the shinytree is not present from the beginning):
$(function() {
$('#create').on('click', function() {
$('#mytree').jstree({
'core' : {
'data' : {
"url" : "//www.jstree.com/fiddle/?lazy",
"data" : function (node) {
return { "id" : node.id };
}
}
},
contextmenu : {
items : {
'item1' : {
'label' : 'item1',
'action' : function () { /* action */ }
}
}
},
plugins : ["contextmenu"]
});
})
})
<link href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.8/themes/default/style.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.8/jstree.min.js"></script>
<div id="mytree"></div>
<button id = "create">
Create Tree
</button>
The problem is that I cannot provide the contextmenu via shinyTree. So I need to fall back to JavaScript to add this functionality myself. On the console I would do it like follows:
$('#mytree').jstree(true).settings.contextmenu = {
items: {
item1 : {
'label' : 'item1',
'action': function() { /* action */ }
}
}
}
But where and when would I call that in my ShinyApp? My approach with another handler does not work, because I guess that the handler fires before the tree is rendered (a second press to the button does the trick then, showing at least that the JS code works as intended). Playing with the priority did not help either.
Could I use a javascript event handler which I would attach to $(document), which listens to the creation of the tree?
ShinyApp
library(shiny)
library(shinyTree)
library(shinyjs)
js_code <- "$('#mytree').jstree(true).settings.contextmenu = {
items: {
item1 : {
'label' : 'item1',
'action': function() { /* action */ }
}
}
};"
ui <- fluidPage(useShinyjs(),
actionButton("create", "Create Tree"),
shinyTree("mytree", contextmenu = TRUE))
server <- function(input, output, session) {
## does not work as intended
observeEvent(input$create, runjs(js_code), ignoreInit = TRUE, priority = -1)
output$mytree <- renderTree({
req(input$create)
list("Root Node" = list("Child Node 1" = list(
"Child Node 3" = "",
"Child Node 4" = ""),
"Child Node 2" = ""))
})
}
shinyApp(ui, server)

I found the solution. Basically one can use the ready.jstree or loaded.jstree event:
library(shiny)
library(shinyTree)
library(shinyjs)
js_code <- "$('.shiny-tree').on('ready.jstree', function() {
$(this).jstree(true).settings.contextmenu = {
items: {
item1 : {
'label' : 'item1',
'action': function() { /* action */ }
}
}
};
})"
ui <- fluidPage(useShinyjs(),
actionButton("create", "Create Tree"),
shinyTree("mytree", contextmenu = TRUE))
server <- function(input, output, session) {
session$onFlushed(function() runjs(js_code))
output$mytree <- renderTree({
req(input$create)
list("Root Node" = list("Child Node 1" = list(
"Child Node 3" = "",
"Child Node 4" = ""),
"Child Node 2" = ""))
})
}
shinyApp(ui, server)

Related

In Shiny need to dynamically update dropdown choices with updateRadioGroupButtons

Following R Shiny group buttons with individual hover dropdown selection, need to update the radiogroupbuttons dynamically based on some condition. The number of buttons may change.
I have at least the following queries related to the code below. 1) Does the tag belong in server? 2) how to dynamically multiply selectInput in the server code? 3) How to dynamically multiply the output? I have changed your implementation to fit closer to my application. All dropdowns have the same choices if the button is to be shown a dropdown, this is computed dynamically in dropdownTRUE. If dropdownTRUE==F, I don't need a dropdown.
library(shiny)
library(shinyWidgets)
js <- "
function qTip() {
$('#THE_INPUT_ID .radiobtn').each(function(i, $el){
var value = $(this).find('input[type=radio]').val();
var selector = '#select' + value;
$(this).qtip({
overwrite: true,
content: {
text: $(selector).parent().parent()
},
position: {
my: 'top left',
at: 'bottom right'
},
show: {
ready: false
},
hide: {
event: 'unfocus'
},
style: {
classes: 'qtip-blue qtip-rounded'
},
events: {
blur: function(event, api) {
api.elements.tooltip.hide();
}
}
});
});
}
function qTip_delayed(x){
setTimeout(function(){qTip();}, 500);
}
$(document).on('shiny:connected', function(){
Shiny.addCustomMessageHandler('qTip', qTip_delayed);
});
"
ui <- fluidPage(
tags$head( # does this belong to server?
tags$link(rel = "stylesheet", href = "jquery.qtip.min.css"),
tags$script(src = "jquery.qtip.min.js"),
tags$script(HTML(js))
),
br(),
uiOutput('bttns'),
verbatimTextOutput("selection1")
)
server <- function(input, output, session) {
session$sendCustomMessage("qTip", "")
output$bttns<-renderUI({
bttnchoices=c("A", "B", "C")
lenchoice=length(bttnchoices)
dropdownTRUE=sample(c(T,F),lenchoice,T,rep(.5,2)) ##bttns for which dropdown is to be shown
dropchoices = c("Apple", "Banana")# same choices to be shown for all buttons with dropdownTRUE
radioGroupButtons(
inputId = "THE_INPUT_ID",
individual = TRUE,
label = "Make a choice: ",
choices = bttnchoices
)
div(
style = "display: none;",
shinyInput(lenchoice,selectInput, # struggling with dynamic multiplication of selectInput, lapply?
"select",
label = "Select a fruit",
choices=dropchoices,
selectize = FALSE
))
})
observeEvent(input[["select1"]], {
if(input[["select1"]] == "Banana"){
session$sendCustomMessage("qTip", "")
output$bttns<-renderUI({
bttnchoices=c("D", "A")
lenchoice=length(bttnchoices)
dropdownTRUE=sample(c(T,F),lenchoice,T,rep(.5,2))
dropchoices = c("Peach", "Pear")
radioGroupButtons(
inputId = "THE_INPUT_ID",
individual = TRUE,
label = "Make a choice: ",
choices = bttnchoices
)
div(
style = "display: none;",
shinyInput(lenchoice,selectInput,
"select",
label = "Select a fruit",
choices = dropchoices,
selectize = FALSE
))
})
}
output$selection1<-input$select1 # struggling with dynamic multiplication of outputs, lapply?
})
}
shinyApp(ui, server)
Here is the way. The values of the radio buttons must correspond to the suffixes of the selectInput's ids. Here A, B, C, D are the values and then the ids of the selectInput are selectA, selectB, selectC, selectD. If you want to use other names for the radio buttons, do choices = list("name1" = "A", "name2" = "B", "name3" = "C", "name4" = "D").
library(shiny)
library(shinyWidgets)
js <- "
function qTip() {
$('#THE_INPUT_ID .radiobtn').each(function(i, $el){
var value = $(this).find('input[type=radio]').val();
var selector = '#select' + value;
$(this).qtip({
overwrite: true,
content: {
text: $(selector).parent().parent()
},
position: {
my: 'top left',
at: 'bottom right'
},
show: {
ready: false
},
hide: {
event: 'unfocus'
},
style: {
classes: 'qtip-blue qtip-rounded'
},
events: {
blur: function(event, api) {
api.elements.tooltip.hide();
}
}
});
});
}
function qTip_delayed(x){
setTimeout(function(){qTip();}, 500);
}
$(document).on('shiny:connected', function(){
Shiny.addCustomMessageHandler('qTip', qTip_delayed);
});
"
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "jquery.qtip.min.css"),
tags$script(src = "jquery.qtip.min.js"),
tags$script(HTML(js))
),
br(),
radioGroupButtons(
inputId = "THE_INPUT_ID",
individual = TRUE,
label = "Make a choice: ",
choices = c("A", "B", "C")
),
br(), br(), br(),
verbatimTextOutput("selectionA"),
verbatimTextOutput("selectionB"),
verbatimTextOutput("selectionC"),
verbatimTextOutput("selectionD"),
div(
style = "display: none;",
selectInput(
"selectA",
label = "Select a fruit",
choices = c("Apple", "Banana"),
selectize = FALSE
),
selectInput(
"selectB",
label = "Select a fruit",
choices = c("Lemon", "Orange"),
selectize = FALSE
),
selectInput(
"selectC",
label = "Select a fruit",
choices = c("Strawberry", "Pineapple"),
selectize = FALSE
),
selectInput(
"selectD",
label = "Select a fruit",
choices = c("Pear", "Peach"),
selectize = FALSE
)
)
)
server <- function(input, output, session) {
session$sendCustomMessage("qTip", "")
output[["selectionA"]] <- renderPrint(input[["selectA"]])
output[["selectionB"]] <- renderPrint(input[["selectB"]])
output[["selectionC"]] <- renderPrint(input[["selectC"]])
output[["selectionD"]] <- renderPrint(input[["selectD"]])
observeEvent(input[["selectA"]], {
if(input[["selectA"]] == "Banana"){
updateRadioGroupButtons(session, inputId = "THE_INPUT_ID",
label = "Make NEW choice: ",
choices = c("D","A"))
session$sendCustomMessage("qTip", "")
}
})
}
shinyApp(ui, server)
EDIT
The following way allows to set dropdowns for a chosen list of radio buttons.
library(shiny)
library(shinyWidgets)
js <- "
function qTip(values, ids) {
$('#THE_INPUT_ID .radiobtn').each(function(i, $el){
var value = $(this).find('input[type=radio]').val();
if(values.indexOf(value) > -1){
var selector = '#' + ids[value];
$(this).qtip({
overwrite: true,
content: {
text: $(selector).parent().parent()
},
position: {
my: 'top left',
at: 'bottom right'
},
show: {
ready: false
},
hide: {
event: 'unfocus'
},
style: {
classes: 'qtip-blue qtip-rounded'
},
events: {
blur: function(event, api) {
api.elements.tooltip.hide();
}
}
});
}
});
}
function qTip_delayed(mssg){
$('[data-hasqtip]').qtip('destroy', true);
setTimeout(function(){qTip(mssg.values, mssg.ids);}, 500);
}
$(document).on('shiny:connected', function(){
Shiny.addCustomMessageHandler('qTip', qTip_delayed);
});
"
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "jquery.qtip.min.css"),
tags$script(src = "jquery.qtip.min.js"),
tags$script(HTML(js))
),
br(),
radioGroupButtons(
inputId = "THE_INPUT_ID",
individual = TRUE,
label = "Make a choice: ",
choices = c("A", "B", "C")
),
br(), br(), br(),
uiOutput("selections"),
uiOutput("dropdowns")
)
server <- function(input, output, session) {
dropdowns <- reactiveVal(list( # initial dropdowns
A = c("Apple", "Banana"),
B = c("Lemon", "Orange"),
C = c("Strawberry", "Pineapple")
))
flag <- reactiveVal(FALSE)
prefix <- reactiveVal("")
observeEvent(dropdowns(), {
if(flag()) prefix(paste0("x",prefix()))
flag(TRUE)
}, priority = 2)
observeEvent(input[["selectA"]], {
if(input[["selectA"]] == "Banana"){
updateRadioGroupButtons(session, inputId = "THE_INPUT_ID",
label = "Make NEW choice: ",
choices = c("D","A","B"))
dropdowns( # new dropdowns, only for D and B
list(
D = c("Pear", "Peach"),
B = c("Watermelon", "Mango")
)
)
}
})
observeEvent(dropdowns(), {
req(dropdowns())
session$sendCustomMessage(
"qTip",
list(
values = as.list(names(dropdowns())),
ids = setNames(
as.list(paste0(prefix(), "select", names(dropdowns()))),
names(dropdowns())
)
)
)
})
observeEvent(dropdowns(), {
req(dropdowns())
lapply(names(dropdowns()), function(value){
output[[paste0("selection",value)]] <-
renderPrint(input[[paste0(prefix(), "select", value)]])
})
})
output[["dropdowns"]] <- renderUI({
req(dropdowns())
selectInputs <- lapply(names(dropdowns()), function(value){
div(style = "display: none;",
selectInput(
paste0(prefix(), "select", value),
label = "Select a fruit",
choices = dropdowns()[[value]],
selectize = FALSE
)
)
})
do.call(tagList, selectInputs)
})
output[["selections"]] <- renderUI({
req(dropdowns())
verbOutputs <- lapply(names(dropdowns()), function(value){
verbatimTextOutput(
paste0("selection", value)
)
})
do.call(tagList, verbOutputs)
})
}
shinyApp(ui, server)

R Shiny with MutationObserver

I'd like to pass info that shiny has class shiny-busy to server side using Shiny.onInputChange. I almost did it using MutationObserver:
isShinyBusy.js in www/ directory:
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var attributeValue = $(mutation.target).prop(mutation.attributeName);
if (mutation.attributeName === "class" && attributeValue == "shiny-busy") {
console.log("Class attribute changed to:", attributeValue);
Shiny.onInputChange("isShinyBusy", true);
} else {
Shiny.onInputChange("isShinyBusy", false);
}
});
});
$( document ).ready(function() {
observer.observe($("html")[0], {
attributes: true
});
});
app.R:
library(shiny)
shinyApp(
ui = fluidPage(
tags$head(tags$script(src = "isShinyBusy.js")),
br(),
actionButton("btn", "Click")
),
server = function(input, output) {
observeEvent(input$btn, ignoreInit = TRUE, {
Sys.sleep(5)
message("Button is pressed")
})
observe({
is.shiny.busy <- input$isShinyBusy
message("Shiny is busy: ", is.shiny.busy)
})
}
)
but that observer triggers many times as you can see in web browser console:
and in rstudio terminal:
Could you explain why this is happening?

Dynamic data added in custom TinyMCE Editor using AngularJs

I am using anglarjs TinyMCE editor, https://www.tinymce.com/docs/integrations/angularjs/, here, I added custom dropdown button in toolbox, and Its working fine when I used static value,but I don't know actually how can I loaded dynamic data value in this dropdownlist.
setup : function ( editor ) {
editor.addButton( 'customDrpdwn', {
text : 'Customers List',
type: 'menubutton',
icon : false,
menu: [
{
text: 'Customer 1',
onclick: function(){
alert("Clicked on Customer 1");
}
},
{
text: 'Customer 2',
onclick: function(){
alert("Clicked on Customer 2");
}
}
]
});
},
};
I try myself to load dynamic value in Menu text field, but I'm getting error. After dynamic loading my code like this -
$scope.customerList = ['Customer 1','Customer 2'];
setup : function ( editor ) {
editor.addButton( 'customDrpdwn', {
text : 'Customers List',
type: 'menubutton',
icon : false,
for(var i =0; i< $scope.customerList.length; i++){
menu: [
{
text: $scope.customerList[i],
onclick: function(){
alert("Clicked on Customer 1");
}
}
]
}
});
}
Now, my question is that, this is possible to load dynamic data in this custom field. If so then how can I load data dynamically? Please help me.
here is one way to do this:
$scope.customerList = ['Customer 1','Customer 2'];
// first make all the menu items
var menuItems = [];
$scope.customerList.forEach(function(customer, index){
item = {
'text': customer,
onclick: function(){
alert("Clicked on " + customer);
}
};
menuItems.push(item);
});
$scope.tinymceOptions = {
plugins: 'link image code',
toolbar: 'undo redo | bold italic | alignleft aligncenter alignright | code | customDrpdwn',
setup: function(editor){
editor.addButton( 'customDrpdwn', {
text : 'Customers List',
type: 'menubutton',
icon : false,
menu: menuItems // then just add it here
});
}
};

Mithril: defer route configuration until DOM is ready

My app has div in its view, which will be used as mounting point for pages of my app.
app.view = function(ctrl) {
return [
appHeader.view(),
appNav.view(),
m("#page")
];
};
The following doesn't seem to work:
m.mount(document.getElementById("app"), app);
m.route.mode = "hash";
m.route(document.getElementById("page"), "", {
"select_company": admin.SelectCompany
});
It works if I include <div id="page"></div> directly in app.html.
How to solve the above issue, without writing html directly?
I was told by #ArthurClemens and #barneycarroll through Gitter chat that using m.mount() and m.route() both in one application is not recommended approach. One solution provided by #barneycarroll is to only use m.route(), and use a function that will return page view along with other common parts of the application like below (jsbin is here):
var header = {
view : function(){
return m( "h1", "This is the persistent site header" )
}
}
var nav = {
controller : function(){
this.links = [
[ "/", "Home" ],
[ "/select_company", "Companies" ]
]
},
view : function( ctrl ){
return m( "ul",
ctrl.links.map( function( link ){
return m( "li",
m( "a", {
config : m.route,
href : link[ 0 ]
}, link[ 1 ] )
)
} )
)
}
}
function furnish( component ){
return {
view : function(){
return [
header,
nav,
component
]
}
}
}
var home = {
view : function(){
return m( "h2", "Welcome!" )
}
}
var selectCompany = {
view : function(){
return m( "h2", "Please select a company" )
}
}
m.route.mode = "hash";
m.route( document.body, "/", {
"/" : furnish( home ),
"/select_company": furnish( selectCompany )
} );

Dynamic Arrays for Jquery Plugins

I am building a simple JQuery plugin in which a user can build modules that contain a title, subtitle, and text.
I want to allow the user to be able to add multiple elements to the module in one plugin call.
var methods = {
build : function( options ) {
var settings = $.extend( {
'header' : 'Untitled',
'subtitle' : 'Subtitle',
'content' : 'Lorem ipsum dolor amet...',
'img' : '' //img URL
}, options);
//Create the div to hold elements
var box = $('<div/>', {
class: 'service'
}).appendTo(this);
//Populate div with header
$('<h2/>', {
class: 'service-title',
text: settings.header
}).appendTo(box);
//Check for subtitle
if(settings.subtitle != 'Subtitle') {
$('<h4/>', {
class: 'service-sub',
text: settings.subtitle
}).appendTo(box);
}
//Add body text
$('<p/>', {
class: 'service-text',
text: settings.content
}).appendTo(box);
//Check for image
if(settings.img != '') {
$('<img/>', {
class: 'service-img',
src: settings.img
}).appendTo(box);
}
},
add : function() {
}
};
$.fn.service = function( method ) {
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1));
} else if ( typeof method === 'object' || ! method ) {
return methods.build.apply( this, arguments );
} else {
$.error( "Method " + method + " does not exist for the Servicer" );
}
};
})( jQuery );
In the variable settings, under subtitle, I would like to allow users to add more than one subtitle within one call to .service()
Allow user to make subtitle option an array instead of string. Test if it is a string or array ,if it is an array run a loop.

Categories

Resources