Shiny dynamic buttons - user-interface

quite a tricky situation here.
I am trying to generate a UI to manage projects in an App.
What I do is that each user has his list of projects stored somewhere, and from this list of project I generate, for each project, a div containing four buttons (later buttonTitle, continueProj, deleteProj and shareProj). Each of these buttons, when clicked, is supposed to trigger a function on the corresponding project.
To handle this, each time I get to the panel corresponding to this UI, I empty and generate an UI by project with insertUI, and fill a list for each action.
for (i in 1:(length(list_ui))){
removeUI(
selector = paste0('#',i)
)
}
tit_obsList <<- list()
con_obsList <<- list()
del_obsList <<- list()
sha_obsList <<- list()
list_ui <<- list()
if(length(USER$list_of_projects !=0)){
for (i in 1:length(USER$list_of_projects)){
insertUI(
selector = '#placeholder',
ui = tags$div(
#class = "ProjectPanel",
style = "background: #ffffff;
display: block;
margin-left: auto;
margin-right: auto;
width:1250px;
height:175px;
background-color: #ffffff;
color: #4a86e8;
border: #434343;",
fluidRow(column(width = 11,
actionButton(inputId = paste0("buttonTitle_",USER$list_of_projects[i]),label = USER$list_of_projects[i],style = "background : #4a86e8;background-color: transparent;border-color : transparent; color: #4a86e8; font-size : 30px;")),
#tags$h3(paste(USER$list_of_projects[i]))),
column(width = 1,
tags$button(id = paste0("continueProj_",USER$list_of_projects[i]),class = "btn action-button",img(src = "icons/continue.gif",height = "40px"),style = "background-color: transparent;"))),
fluidRow(column(width = 1,offset = 11,
tags$button(id = paste0("deleteProj_",USER$list_of_projects[i]),class = "btn action-button",img(src = "icons/delete_blue.png",height = "40px"),style = "background-color: transparent;"))),
fluidRow(column(width = 1,offset = 11,
tags$button(id = paste0("shareProj_",USER$list_of_projects[i]),class = "btn action-button",img(src = "icons/share.gif",height = "40px"),style = "background-color: transparent;"))),
id = i))
#addPopover(session,paste0("deleteProj_",USER$list_of_projects[i]),"Hello")
list_ui <<- c(list_ui,i)
if (is.null(tit_obsList[[paste0("buttonTitle_",USER$list_of_projects[i])]])){
# make sure to use <<- to update global variable obsList
tit_obsList[[paste0("buttonTitle_",USER$list_of_projects[i])]] <<- observeEvent(input[[paste0("buttonTitle_",USER$list_of_projects[i])]], {
continue_project(USER$list_of_projects[i])
})
}
if (is.null(con_obsList[[paste0("continueProj_",USER$list_of_projects[i])]])){
# make sure to use <<- to update global variable obsList
con_obsList[[paste0("continueProj_",USER$list_of_projects[i])]] <<- observeEvent(input[[paste0("continueProj_",USER$list_of_projects[i])]], {
continue_project(USER$list_of_projects[i])
})
}
if (is.null(del_obsList[[paste0("deleteProj_",USER$list_of_projects[i])]])){
# make sure to use <<- to update global variable obsList
del_obsList[[paste0("deleteProj_",USER$list_of_projects[i])]] <<- observeEvent(input[[paste0("deleteProj_",USER$list_of_projects[i])]], {
delete_project(USER$ID,USER$list_of_projects[i])
removeUI(
selector = paste0('#', i)
)
})
}
if (is.null(sha_obsList[[paste0("shareProj_",USER$list_of_projects[i])]])){
# make sure to use <<- to update global variable obsList
sha_obsList[[paste0("shareProj_",USER$list_of_projects[i])]] <- observeEvent(input[[paste0("shareProj_",USER$list_of_projects[i])]], {
showModal(modalDialog(
title = "You have decided to share your project",
textInput("receiver_name","Please state the Login of the user you want to share your project with :"),
hidden(p(id="enter_receiver","Please enter a user to share this project with ",style="font-size:10px;color:red;")),
hidden(p(id="receiver_does_not_exist","This user does not exist",style="font-size:10px;color:red;")),
hidden(p(id="receiver_has_access","This user already has access to this project",style="font-size:10px;color:red;")),
footer = tagList(
modalButton("Cancel"),
actionButton("ok_shareProj", "OK")
),
easyClose = TRUE
)
)
if(input$receiver_name == ""){
shinyjs::hide("receiver_does_not_exist")
shinyjs::show("enter_receiver")
shinyjs::hide("receiver_has_access")
}
else if(USER_in_database(input$receiver_name)==FALSE){
shinyjs::show("receiver_does_not_exist")
shinyjs::hide("enter_receiver")
shinyjs::hide("receiver_has_access")
}
else if(has_access(input$receiver_name,USER$list_of_projects[i])){
shinyjs::hide("receiver_does_not_exist")
shinyjs::hide("enter_receiver")
shinyjs::show("receiver_has_access")
}
else{
invite_to_project(USER$ID,input$receiver_name,USER$list_of_projects[i])
removeModal()
}
})
}
}
output$listproj <- renderText({unlist(USER$list_of_projects)})
output$lenproj <- renderText({length(USER$list_of_projects)})
output$uis <- renderText({unlist(tit_obsList)})
}
What is extraneous now is that, if I have no project, I can create them and all is working well : every button is functional.
But if I reload the app, all UIs are present, but only the last button is working : if I have 3 projects P1, P2 and P3, only P3 buttons work, or more precisely, if I click to delete P2, nothing happens, but now if I click to continue P3, both events are triggered and P2 is deleted while P3 is continued.
Another strange issue is that if I am in this same P1, P2, P3 situation and I delete P3, then P2 and only P2, the latter one, is functional again, while it was not before P3 was deleted. That means only the last one works.
I repeat that this only happens when I reload the app. If the app is not reloaded, all projects are functional, which is surprising because the list from which they are generated is the same and the place from which I call the code above is the same.
I hope someone can help me tackle this issue, thank you

Related

Web Animations API — how to play backwards rather than reverse?

The Web Animations API has the reverse method which allows the running animation to be played back in the opposite direction it is currently headed.
However I have a scenario where I would just like to play a defined animation backwards. At present, I am accomplishing this by checking if the animation is going forwards or backwards first and then doing the opposite but this feels wonky!
Is there an established pattern for just asking a defined animation to run backwards (and not necessarily run in reverse from its current playhead)?
In code example below, if you are on the first instance (idx === 0) I check if you are going forwards by checking if playbackRate is 1 first. Without this, the animation would run backwards first.
Feels like there should be a simpler way of running an animation backwards when required — is there?
var box = document.querySelector(".thing");
var btn = document.querySelector("button");
var span = document.querySelector("span");
const move = box.animate(
[{ transform: "translate(0px, 0px)" }, { transform: "translate(100px, 0px)" }],
{ duration: 500, fill: "both" }
);
move.pause();
let idx = 0;
function choose() {
if (idx === 0) {
move.playbackRate === 1 ? move.play() : move.reverse();
} else {
move.reverse();
}
}
btn.addEventListener("click", () => {
choose();
idx = idx === 0 ? 1 : 0;
span.textContent = idx;
});
.thing {
width:49px;
height: 49px;
background-color: red;
}
<div class="thing"></div>
<button type="button">ClickMe</button>
<span>0</span>
I believe you want the direction property, e.g.
move.effect.updateTiming({ direction: 'reverse' });

How to modify a js variable within a shiny server (htmlwidgets/R)

This question is more conceptual than explicit to a particular piece of code so I guess there's no toy-code this time.
I have a rather large shiny app that uses some d3.js scripts. The D3 objects access a few global variables when their update functions are called. I'd like to control these variables from the Shiny server - is this possible?
This app assumes the global variable was assigned to the window. Then it uses a custom message handler to create the original variable (#1) and another to manipulate the value of that variable (#2).
Edited to have message sent back to R with the state of the global variable.
R Code:
library(shiny)
ui <- fluidPage(
# Application title
titlePanel("Global Variable Manipulation"),
mainPanel(
sliderInput("data", "Element", min = 0, max = 100, value = 0),
actionButton("go", "GO"),
textOutput("var"),
singleton(
tags$head(tags$script(src = "message-handler.js"))
),
singleton(
tags$head(tags$script(src = "message-handler2.js"))
),
singleton(
tags$head(tags$script(src = "back-to-shiny.js"))
)
)
)
server <- function(input, output, session) {
observe({
session$sendCustomMessage(type = 'testmessage',
message = list(a = 1, b = 'text',
controller = input$data))
})
observeEvent(input$go,{
session$sendCustomMessage(type = 'changeMessage',
message = list(2))
})
output$var <- renderPrint({
input$jsvalue
})
}
# Run the application
shinyApp(ui = ui, server = server)
message-handler.js code:
Shiny.addCustomMessageHandler("testmessage",
function(message) {
window.globalVar = message.controller
alert(JSON.stringify(window.globalVar));
}
);
message-handler2.js code:
Shiny.addCustomMessageHandler("changeMessage",
function(message) {
window.globalVar = window.globalVar-message;
alert(JSON.stringify(window.globalVar));
}
);
back-to-shiny.js code:
setInterval(function(){
var message = window.globalVar;
Shiny.onInputChange("jsvalue", message);
},0);

Do not automatically expand all subgrid rows when clicking grouping column's expand icon

After grouping, is there a way for expand/collapse icon of current row not automatically expand/collapse all of the subgrid's rows? Just leave it alone as it was.
var parmColumnName = 'Model';
$('#test').jqGrid('groupingGroupBy'),
parmColumnName,
{
groupCollapse: true,
groupField: ['name']
}
);
//Original setup after playing around with it. (See X5 under BMW)
//Collapse the grouped Make
//Then Expand the grouped Make (All of the model are expanded by default, I do not want it to change and I want it to look like the original snapshot above)
I find your question very interesting, but the solution for the problem is not easy. In my opinion the source code of two jqGrid methods groupingRender and especially groupingToggle should be changed. The solution which I suggest you can see on the demo. The demo overwrites the original code of groupingRender and groupingToggle methods. More full description of my suggestions you will find below.
First of all I want to describe the problem in my words. You used the words "the subgrid's rows" in the text of your question which bring misunderstanding. What you use is multilevel grouping. The first problem in my opinion is the behavior of groupCollapse: true option. In case of multilevel grouping jqGrid collapse currently only data instead of all grouping headers till the top level. The demo uses 3-level grouping and the option groupCollapse: true. It dysplays
instead of intuitively expected
Another problem which you formulate in your question is the current behavior of expending. The problem is that if the user have collapsed the nodes to that all looks compact, like on the last picture which I posted, end then the user expand some node jqGrid expand all children grouping headers of the node till the data. So if one expand for example only "test1" node then all its children nodes will be expanded instead of expending only the next grouping level.
To fix the first problem (opened sub-grouping headers in spite of groupCollapse: true) I changed one line of groupingRender method from
str += "<tr id=\""+hid+"\" role=\"row\" class= \"ui-widget-content jqgroup ui-row-"+$t.p.direction+" "+clid+"\"><td style=\"padding-left:"+(n.idx * 12) + "px;"+"\" colspan=\""+colspans+"\">"+icon+$.jgrid.template(grp.groupText[n.idx], gv, n.cnt, n.summary)+"</td></tr>";
to
str += "<tr id=\""+hid+"\"" +(grp.groupCollapse && n.idx>0 ? " style=\"display:none;\" " : " ") + "role=\"row\" class= \"ui-widget-content jqgroup ui-row-"+$t.p.direction+" "+clid+"\"><td style=\"padding-left:"+(n.idx * 12) + "px;"+"\" colspan=\""+colspans+"\">"+icon+$.jgrid.template(grp.groupText[n.idx], gv, n.cnt, n.summary)+"</td></tr>";
The main problem which you asked was a little more difficult. Below you can find the fixed version of
$.jgrid.extend({
groupingToggle : function(hid){
this.each(function(){
var $t = this,
grp = $t.p.groupingView,
strpos = hid.split('_'),
uidpos,
//uid = hid.substring(0,strpos+1),
num = parseInt(strpos[strpos.length-2], 10);
strpos.splice(strpos.length-2,2);
var uid = strpos.join("_"),
minus = grp.minusicon,
plus = grp.plusicon,
tar = $("#"+$.jgrid.jqID(hid)),
r = tar.length ? tar[0].nextSibling : null,
tarspan = $("#"+$.jgrid.jqID(hid)+" span."+"tree-wrap-"+$t.p.direction),
getGroupingLevelFromClass = function (className) {
var nums = $.map(className.split(" "), function (item) {
if (item.substring(0, uid.length + 1) === uid + "_") {
return parseInt(item.substring(uid.length + 1), 10);
}
});
return nums.length > 0 ? nums[0] : undefined;
},
itemGroupingLevel,
collapsed = false, tspan;
if( tarspan.hasClass(minus) ) {
if(grp.showSummaryOnHide) {
if(r){
while(r) {
if($(r).hasClass('jqfoot') ) {
var lv = parseInt($(r).attr("jqfootlevel"),10);
if( lv <= num) {
break;
}
}
$(r).hide();
r = r.nextSibling;
}
}
} else {
if(r){
while(r) {
itemGroupingLevel = getGroupingLevelFromClass(r.className);
if (itemGroupingLevel !== undefined && itemGroupingLevel <= num) {
break;
}
$(r).hide();
r = r.nextSibling;
}
}
}
tarspan.removeClass(minus).addClass(plus);
collapsed = true;
} else {
if(r){
var showData = undefined;
while(r) {
itemGroupingLevel = getGroupingLevelFromClass(r.className);
if (showData === undefined) {
showData = itemGroupingLevel === undefined; // if the first row after the opening group is data row then show the data rows
}
if (itemGroupingLevel !== undefined) {
if (itemGroupingLevel <= num) {
break;// next item of the same lever are found
} else if (itemGroupingLevel === num + 1) {
$(r).show().find(">td>span."+"tree-wrap-"+$t.p.direction).removeClass(minus).addClass(plus);
}
} else if (showData) {
$(r).show();
}
r = r.nextSibling;
}
}
tarspan.removeClass(plus).addClass(minus);
}
$($t).triggerHandler("jqGridGroupingClickGroup", [hid , collapsed]);
if( $.isFunction($t.p.onClickGroup)) { $t.p.onClickGroup.call($t, hid , collapsed); }
});
return false;
},
});
The demo demonstrates the results of all changes which I suggest. I'll post the changes as pull request to trirand. I hope that the changes will be included in the main code of jqGrid.
UPDATED: I posted the pull request with the changes which I suggested above.
UPDATED 2: My pull request was merged with the main code of jqGrid. The new 4.5.4 version of jqGrid published today includes the changed. The new demo uses jqGrid 4.5.4 and it works like you expect. So to fix the problem which you described in your question you need just update jqGrid.

SVG ― click halts animation in Chromium and Safari

right now I have totally no idea what could cause this strange bug and I hope to able to describe it as good as possible, but the code has grown big and to post all of it wont result in a clear question.
1. Setup:
A Box2dWeb animation in the background, where the triggering of the Step() method is done by window.requestAnimationFrame following the game loop implementation here in the »That's it« section. Every time the »draw« method is called, the b2body's transformation is handed over to a <div> and to a <g>. The <div> sets up the desired animation and the <g> acts as monitor of the body and resides into an inline <svg>.
Clicks are globally caught and if the user clicks on the <div> or on the <path> the target gains focus what leads to applying forces on the b2body.
2. Bug:
All in all everything works as expected, the animation runs fluid for the <div> and its corresponding <g>, clicking on both, the focus state changes correctly, BUT in chromium and Safari the <g> stops showing up the animation, when the user clicked on the <g>.
Here is the code that is used to set the transformation from the b2Body to the <g>:
function draw(){
var str = this._sprite.transform;
var mtrs = this._transformItem.matrix;
mtrs.a = str.a1;
mtrs.c = str.a2;
mtrs.e = str.a3;
mtrs.b = str.b1;
mtrs.d = str.b2;
mtrs.f = str.b3;
this._transformItem.setMatrix( mtrs );
}
where this._transformItem points to <g>.transform.baseVal.getItem( 0 ) and this._sprite.transform to a transform matrix of custom typ.
Here is the code to catch the clicks:
function getClickTargets( target ){
var targets = { 'sprite' : null, 'link' : null };
while( target ){
if( target.nodeName.toLowerCase() === 'a' &&
targets.link == null ){
targets.link = target;
}
if( target.hasAttribute &&
target.hasAttribute( 'data-sprite-id' ) &&
targets.sprite == null ){
targets.sprite = this._stage.getSpriteById(
target.getAttribute( 'data-sprite-id' ) );
}
if( target.nodeName.toLowerCase() == 'path' ){
var attr = target.getAttributeNS( NS, 'monitor-for-sprite' );
if( attr != '' ){
targets.sprite = this._stage.getSpriteById( attr );
}
}
if( targets.link != null && target.sprite != null ){
break;
}
target = target.parentNode;
}
return targets;
};
In FF, IE9/10, Chrome and Opera everything runs as it should and the only thing that causes the misbehavior is that a »click« happens with a <path> as target. The matrices a correct because the animation keeps on running correct for the <div>.
What could cause this, where should I search, does anybody has an idea or had a similar bug?
EDIT:
Might this be caused by the fact that the events are caught and processed »asynchronously« to the overall running »update ticks« ?
Edit 2:
I noticed that the same problem occurs if I am starting the web inspector and watch the elements, but than all <g> elements freeze.
Edit 3:
I have got it running now, at least in chromium, but I guess Safari will do it also. I slightly changed the »draw« function to this:
function(){
var str = this._sprite.transform;
var mtrs = this._transformItem.matrix;
mtrs.a = str.a1;
mtrs.c = str.a2;
mtrs.e = str.a3;
mtrs.b = str.b1;
mtrs.d = str.b2;
mtrs.f = str.b3;
//this._transformItem.setMatrix( mtrs ); //old line
this._transformList.replaceItem(
this._transformList.createSVGTransformFromMatrix( mtrs ), 0 ); //new working line
}
If somebody knows why it only works with a new »SVGTransform« it would be nice to let me know. Otherwise I assume that this is a kind of bug.

Magento Enterprise Tabs - How to select specific tab in link?

I am trying to link to a specific tab in Magento Enterprise. It seems that all of the answers I've found don't apply well to their method. I just need a link to the page to also pull up a specific tab. This is the code they use:
Enterprise.Tabs = Class.create();
Object.extend(Enterprise.Tabs.prototype, {
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
this.activeTab = this.tabs.first();
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
},
handleTabClick: function (evt) {
this.activeTab = Event.findElement(evt, 'dt');
this.select();
},
select: function () {
for (var i = 0, l = this.tabs.length; i < l; i ++) {
if (this.tabs[i] == this.activeTab) {
this.tabs[i].addClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 2;
/*this.tabs[i].next('dd').show();*/
new Effect.Appear (this.tabs[i].next('dd'), { duration:0.5 });
this.tabs[i].parentNode.style.height=this.tabs[i].next('dd').getHeight() + 15 + 'px';
} else {
this.tabs[i].removeClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 1 - i;
this.tabs[i].next('dd').hide();
}
}
}
});
Anyone have an idea?
I would consider modifying how the class starts up.
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
// change starts here //
var hashTab = $(window.location.hash.slice(1));
this.activeTab = ( this.tabs.include(hashTab) ? hashTab : this.tabs.first());
// change ends here //
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
}
Here, I have only changed how the initial tab is chosen. It checks for an URL fragment which is commonly known as a hash, if that identifies one of the tabs it is preselected. As a bonus the browser will also scroll to that element if possible.
Then you only need to append the tab's ID to the URL. For example you might generate the URL by;
$productUrl = Mage::getUrl('catalog/product/view', array(
'id' => $productId,
'_fragment' => 'tab_id',
));
If you've recently migrated from an earlier Magento release, e.g. from Enterprise 1.11 to Enterprise 1.12, make sure the javascript in /template/catalog/product/view.phtml
right after the foreach that generates the tabs gets updated to the 1.12 version:
<script type="text/javascript">
var collateralTabs = new Enterprise.Tabs('collateral-tabs');
Event.observe(window, 'load', function() {
collateralTabs.select();
});
</script>
surfimp's VERY helpful suggestions did not produce the desired opening of the closed tab otherwise. Once this updated javascript was added, clicking on a link to read Review or Add Your Review on the product page, jumped to the Reviews tab, even if the tab had been hidden.
Similar to Zifius' answer, you can modify the initialize function to just take another argument which will be the active tab.
Event.observe(window, 'load', function() {
new Enterprise.Tabs('collateral-tabs', $('tab_review'));
});
and then in the scripts.js (or wherever this class may exist for you)
initialize: function (container, el) {
...
this.activeTab = el;
...
}
Use whatever logic in the template you like to set 'el' to the desired value.
The reason I did it this way is because when I used Zifius' method, the desired tab would be the active tab, but the default tab's content was still displayed.
Had the same task yesterday and as I don't know about prototype much I solved it by adding another method:
selectTab: function (element) {
this.activeTab = element;
this.select();
},
Usage:
var Tabs = new Enterprise.Tabs('collateral-tabs');
Tabs.selectTab($('tabId'));
Would like to know if it's a correct approach

Resources