How to force going to top of page in ShinyR tabItem with conditional panels? - shinydashboard

I'm putting together a shinyApp to work as an survey board for number of psychological questionnaires.
Every tab is another study, and inside an app there is a number of conditional panels (as next pages of the survey). To navigate throught pages, there is another panel at the end of page. After changing pages I would like the browser to navigate to the top of new page (the beginning of next questionnaire).
I've tried to base a solution on How to add a "back to top of page" button in R Shiny?, but with observeEvent being the change to the slider. Unfortunately, it won't work. As a matter of fact, it seem to not do anything.
I've created an example of the problem in the code below:
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
##### Shinyjs Code
jscode <- "shinyjs.toTop = function() {document.body.scrollTop = 0;}"
UI <- dashboardPage(skin = "black",
# Title of app
dashboardHeader(title = "Minimal working example", titleWidth = 600),
# Tabs setup
dashboardSidebar(sidebarMenu(menuItem("Info",
tabName = "info",
icon = icon("info")),
menuItem("Survey",
tabName = "survey",
icon = icon("chart-bar"))
)
),
# Main body with two tabs
dashboardBody(
tabItems(
# Info about
tabItem(tabName = "info", tags$title("Info"),
fluidPage(
box(title = "Hi!", width = 40,
"This is the first tab of example"))),
# Survey
tabItem(tabName = "survey", tags$title("Survey"),
#Page first, Shinyjs implementation
fluidPage(useShinyjs(),
extendShinyjs(text = jscode,
functions = "toTop"),
conditionalPanel("input.slider_page == 'page1'",
tags$h1("First page of survey"),
"Now a lot of text",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"More text",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"And now the slider to choose the page"),
# Second page
conditionalPanel("input.slider_page == 'page2'",
tags$h2("Second page of the survey"),
"This should be visible after turning the page",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"More text",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"And this should not be visible just after turning pages"),
#Slider to change a page
fluidRow(sliderTextInput("slider_page",
label = "Choose a page",
choices = c("page1",
"page2"),
selected = c("page1")))
)
))))
server <- function(input, output) {
# observeEvent to execute javascript to go to the top of the page
observeEvent(input$slider_page,{
js$toTop();
})
}
shinyApp(UI, server)

Ok, so I've finally got it. I've found the accepted answer from R Shiny Dashboard Scroll to Top on Button Click to work in my code.
If you need to utilize the same functionality for multiple tabs, which have multiple pages and sliders, you may study the answers to this question: How to listen for more than one event expression within a Shiny observeEvent
Below I paste my modified minimal reproductive example - it may help anyone with similar trouble to mine.
##### Shinyjs Code
UI <- dashboardPage(skin = "black",
# Title of app
dashboardHeader(title = "Minimal working example", titleWidth = 600),
# Tabs setup
dashboardSidebar(sidebarMenu(menuItem("Info",
tabName = "info",
icon = icon("info")),
menuItem("Survey",
tabName = "survey",
icon = icon("chart-bar"))
)
),
# Main body with two tabs
dashboardBody(
tabItems(
# Info about
tabItem(tabName = "info", tags$title("Info"),
useShinyjs(), # Only this call is needed in UI to initialize ShinyJS
fluidPage(
box(title = "Hi!", width = 40,
"This is the first tab of example"))),
# Survey
tabItem(tabName = "survey", tags$title("Survey"),
fluidPage(
conditionalPanel("input.slider_page == 'page1'",
tags$h1("First page of survey"),
"Now a lot of text",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"More text",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"And now the slider to choose the page"),
# Second page
conditionalPanel("input.slider_page == 'page2'",
tags$h2("Second page of the survey"),
"This should be visible after turning the page",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"More text",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"Some more items",
hr(),hr(),hr(),hr(),hr(), hr(),hr(),
"And this should not be visible just after turning pages"),
#Slider to change a page
fluidRow(sliderTextInput("slider_page",
label = "Choose a page",
choices = c("page1",
"page2"),
selected = c("page1")))
)
))))
server <- function(input, output) {
# observeEvent to execute javascript to go to the top of the page
observe({
# You can add multiple triggers here if there are multiple (n) tabs with changing pages in your app
input$slider_page
# input$slider_2_page
# ....
# input$slider_n_page
# And this JS code works finally :)
shinyjs::runjs("window.scrollTo(0, 0)")
})
}
shinyApp(UI, server)

Related

Layout questions with Style Elements and Elm

I am building my first app in Elm and decided to use Style Elements package instead of CSS.
This is the layout that I am attempting. Its a single page app that does not scroll.
here is some code
view : Model -> Html Msg
view model =
layout Styles.stylesheet <|
pageWrapper
pageWrapper : Element Styles variation Msg
column Page
[ width (percent 100), height (percent 100) ]
[ navMain
, contentArea
]
navMain : Element Styles variation Msg
navMain =
row Navigation
[ spread, padding 10 ]
[ el (Nav Logo) [ width (px 50)] (text "Logo")
, row (Nav Link)
[ padding 15, spacing 10]
[ el (Nav Link) [] (text "About")
, el (Nav Link) [] (text "Services")
, el (Nav Link) [] (text "Team")
, el (Nav Link) [] (text "Location")
, el (Nav Link) [] (text "Contact")
]
]
contentArea : Element Styles variation Msg
contentArea =
-- At this point I thought I would make a row with an el with the image
and a column containing the other two images. And other than creating heights with pixels I don't know how to extend the main content to the bottom of the page.
What are some good example apps I can look at to get a good idea of how to control the layout? I have looked through several, and I feel like I am just missing something very obvious because so far SE has been awesome to work with!
Here's a very basic example:
https://ellie-app.com/k25rby75Da1/1
I've left the heights of the elements undefined below but you should be able to figure that out to suit your needs. I updated the ellie link to a version that I think better approximates the height in your example.
module Main exposing (main)
import Html
import Html.Attributes
import Color
import Style
import Style.Font as Font
import Style.Color as Color
import Element exposing (..)
import Element.Attributes exposing (..)
main =
Html.beginnerProgram
{ model = model
, update = update
, view = view
}
type alias Model =
{ navbar : String
, leftcontent : String
, rightcontent : { top : String, bottom : String }
}
model : Model
model =
{ navbar = "This is the navbar"
, leftcontent = "This is the left column content"
, rightcontent =
{ top = "This is the right top content"
, bottom = "This is the right bottom content"
}
}
update model msg =
model
type MyStyles = Navbar | Left | RightTop | RightBottom | None
stylesheet =
Style.styleSheet
[ Style.style Navbar [ Color.background Color.red ]
, Style.style Left [ Color.background Color.blue ]
, Style.style RightTop [Color.background Color.purple ]
, Style.style RightBottom [Color.background Color.gray ]
]
view model =
Element.viewport stylesheet <|
(column None [] [
row Navbar [] [ text model.navbar ]
, wrappedRow None []
[ column Left [ width (percent 50)] [ text model.leftcontent ]
, wrappedColumn None [ width (percent 50)]
[ el RightTop [ width fill] (text model.rightcontent.top)
, el RightBottom [ width fill ] (text model.rightcontent.bottom)
]
]])

Conditionally toggle `preventDefault` on form submissions in Elm

Is there a way to conditionally toggle preventDefault on form submissions?
I've tried using onWithOptions, below, but it seems only the first setting is used, even though I can see it change using Debug.log, when there is a name on the model.
, onWithOptions
"submit"
(if model.name == "" then
(Debug.log "prevent" { preventDefault = True, stopPropagation = True })
else
(Debug.log "allow" { preventDefault = False, stopPropagation = False })
)
(Json.succeed FormSubmit)
Updated with findings from answer
As stated by #Tosh, hiding the button resolves the issue:
, button
[ onPreventClickHtml FormSubmit
, Html.Attributes.hidden (model.name == "" |> not) ]
[ text " prevent" ]
, button
[ onClick FormSubmit
, Html.Attributes.hidden (model.name == "") ]
[ text "allow" ]
Where onPreventClickHtml is:
onPreventClickHtml : Msg -> Attribute Msg
onPreventClickHtml msg =
onWithOptions
"click"
{ preventDefault = True, stopPropagation = True }
(Json.succeed msg)
Conditionally displaying the button does not work:
, (if model.name == "" then
button
[ onPreventClickHtml FormSubmit ]
[ text " prevent" ]
else
button
[ type' "submit", onClick FormSubmit ]
[ text "allow" ]
)
I'm using elm-mdl and so found implementing the solution more challenging because from my experience creating a custom onclick requires types which are not exposed by elm-mdl, and so need to be duplicated.
, if model.name == "" then
Material.Options.nop
else
Material.Options.css "visibility" "hidden"
, onPreventClick FormSubmit
, if model.name == "" then
Material.Options.css "visibility" "hidden"
else
Material.Options.nop
, Button.onClick FormSubmit
onPreventClick : Msg -> Property Msg
onPreventClick message =
Material.Options.set
(\options -> { options | onClick = Just (onPreventClickHtml message) })
-- Duplicated from elm-mdl
type alias Property m =
Material.Options.Property (Config m) m
-- Duplicated from elm-mdl
type alias Config m =
{ ripple : Bool
, onClick : Maybe (Attribute m)
, disabled : Bool
, type' : Maybe String
}
Another approach, which worked for my scenario, was changing the button type:
, if model.name == "" then
Button.type' "reset"
else
Button.type' "submit"
One simple workaround suggestion until someone can show us how to solve it.
How about create two buttons (with different options as you've shown) and depending on the condition show only one of them?
You can use Html.Attributes.hide for that.

Create a combined bar/line chart with dimplejs and use self-defined colors

I'm trying to create a combined bar/line chart based on a simple data set (columns: countries, index1, index2, index3, ...) using dimplejs. Index1 will be the bar chart and index2 upwards should be dynamically (=adding and removing indices per user interaction) displayed as line charts on top.
I found out that I seemingly needed to create a hidden 2nd x-axis for the line graphics. Is there any other way?
I was also unable to add more than one index as line chart. Is there a way to add more lines where each one is based on a column (index2, index3, ...)?
Defining colors was another problem: The bars are all in one color which is good but if I want to assign a color myself I have to give the data a tag which is displayed in the tooltip (not good)?
Assigning colors to the line charts didn't work at all for me. I would need some advice here.
My code so far:
var svg = dimple.newSvg("#graphic", 550, 700);
// Get data for one year
var filteredData = dimple.filterData(data, 'year', '2010');
var myChart = new dimple.chart(svg, filteredData);
myChart.setBounds(50, 30, 480, 630);
var xAxis = myChart.addMeasureAxis('x', 'index1');
xAxis.title = 'Index Value';
xAxis.overrideMax = 1;
var yAxis = myChart.addCategoryAxis('y', 'countries');
yAxis.title = 'Country';
var xAxis2 = myChart.addMeasureAxis('x', 'index2');
xAxis2.title = null;
xAxis2.overrideMax = 1;
xAxis2.hidden = true;
var bars = myChart.addSeries('bars', dimple.plot.bar, [xAxis,yAxis]);
myChart.assignColor('bars', '#D3D3D3', '#D3D3D3');
var lines = myChart.addSeries('index2', dimple.plot.line, [xAxis2,yAxis]);
yAxis.addOrderRule('index1', false);
myChart.draw();
That data structure is not ideal for your data requirement within dimple. The way dimple would like that data is with your index names as dimension values:
var data = [
{ "Index" : "Index 1", "Country" : "UK", "Index Value": 0.2 },
{ "Index" : "Index 1", "Country" : "Spain", "Index Value": 0.7 },
{ "Index" : "Index 2", "Country" : "UK", "Index Value": 0.5 },
{ "Index" : "Index 2", "Country" : "Spain", "Index Value": 0.6 },
{ "Index" : "Index 3", "Country" : "UK", "Index Value": 0.4 },
{ "Index" : "Index 3", "Country" : "Spain", "Index Value": 0.3 },
...
];
Then in order to draw index 1 as a bar and the rest as lines you would need to split your data set into 2:
var barData = [],
lineData = [],
i,
keyIndex = "Index 1";
for (i = 0; i < data.length; i += 1) {
if (data[i]["Index"] === keyIndex) {
barData.push(data[i]);
} else {
lineData.push(data[i]);
}
}
You could then just define your chart as follows:
var chart = new dimple.chart(svg),
bars,
lines;
chart.addMeasureAxis("x", "Index Value");
chart.addCategoryAxis("y", "Country");
bars = chart.addSeries("Index", dimple.plot.bar);
bars.data = barData;
lines = chart.addSeries("Index", dimple.plot.line);
lines.data = lineData;
chart.draw();
I haven't tested this code but it ought to do what you want (minus the formatting etc).
If you want to continue on the road you have started in your code above (which is still possible) you will find composite axes very helpful to avoid hidden secondary axes and potential problems with differing max/min values. See the relevant section in the version 2 release notes for an example of these.
I don't understand why you don't want to tag the indices with something which appears in the tooltip, there must be some difference between them which you can communicate, but if you want to remove the index name from the tooltip you can define custom tooltips as shown here.
Edit: I should add if you just want to change the set of colours which dimple will arbitrarily ascribe to your data points you can override the default colours in the chart object as discussed here.

How to align the header and the column properly in prawn pdf?

We are generating pdf for invoice with prawn in rails 3.1.4.
header =[[ "Date", "#", "sample name", "model","lease item", "hours", "$/hour", "Total"]]
items = #invoice.invoice_items.map do |item|
[
item.lease_usage_record.lease_usage_items.map do |li|
[
li.lease_date.strftime("%Y-%m-%d"),
item.lease_usage_record_id,
li.sample_name,
li.sample_model_num,
li.lease_item.short_name,
li.total_hour,
li.charge_rate,
li.subtotal
]
end
]
end
items = header + items
pdf.table items, :header => true, :width => 480
t = pdf.make_table([ [" ", "Total: "," #{#invoice.total}"] ])
t.draw
It works with a problem. The problem is that all the data cells are squeezed into the first column which is "Date". Tried to add one one more [] to header and it generated error of "data is 2-dimensional array...". How to align each column properly under its header? Thanks so much.
The problem was solved by reducing the dimension of the data (items) to 2 dimension. The prawn can only take 2 dimensional data. The following line does the trick:
items = items.flatten(2)

How to probe rebol layout but just get original spec

Let's take:
panel1: layout [
origin 8x8
h2 "Panel 1"
field "Field 1"
field "Field 2"
button "The Answer" [alert "I know nothing."]
]
If I probe panel1 I got a bunch of lines whereas I'd like to get just the original block:
[
origin 8x8
h2 "Panel 1"
field "Field 1"
field "Field 2"
button "The Answer" [alert "I know nothing."]
]
How to get just this ?
You should simply use another word to hold the block value:
panel1: layout panel-def: [
origin 8x8
h2 "Panel 1"
field "Field 1"
field "Field 2"
button "The Answer" [alert "I know nothing."]
]
probe panel-def
Because layout function returns a face object with lots of default values. By the way use ? instead of probe because there can be self references in face objects so you'll have a endless probe output.