I would like to build a reactive input for R shiny that returns the row.name of a table row on hover.
In R Shiny, the table is made with renderTable and output through tableOutput.
This should work similar to the already built clickID and hoverID for plotOutput (for click & hover respectively).
I don't quite understand the JavaScript or jQuery well enough yet to follow these instructions and build it myself:
http://rstudio.github.io/shiny/tutorial/#building-inputs
Thank you!
Update:
This is the jQuery I have so far:
$(document).on('hover', '.table-hover tr', function(){
var el = $(this);
$(this).closest("tr").index();
});
var selectRowBinding = new Shiny.InputBinding();
$.extend(selectRowBinding, {
find: function(scope) {
return $(scope).find(".table-hover");
},
getValue: function(el){
return $(el).closest("tr").index();
},
setValue: function(el, value) {
},
subscribe: function(el, callback) {
$(el).on("change.selectRowBinding", function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off(".selectRowBinding");
}
});
Shiny.inputBindings.register(selectRowBinding);
but input$selectRowBinding is still returning NULL.
I'm quite sure I have not properly defined the bindings.
I've been working off of these 2 resources:
https://groups.google.com/forum/#!searchin/shiny-discuss/selectablerows/shiny-discuss/ZaYYmFLHA6U/-cL62wQp0HUJ
and
Shiny R application that allows users to modify data
Related
I'm looking at how to turn off reactivity within a template helper function. I only want the data rendered when the template is initially populated and not when the underlying data changes.
My current helper has two reactive variables: one is a Session.get() and the other is a Collection.findOne(). I've tried to wrap the Session.get() in a Tracker.nonreactive() call and set the reactive option to false on the Collection.findOne() but I'm still seeing the reactive behavior.
Template.characterSkills.helpers({
data : function () {
var characterID = Tracker.nonreactive(function () {
return Session.get("currentCharacterID");
});
if(characterID) {
var record = Characters.findOne(
{ _id : characterID },
{
reactive : false,
fields : { skills : 1 }
}
);
if(record && record.skills)
return record.skills;
}
}
});
I've been trying to work this issue for about half a day now. Any help would be greatly appreciated.
You're using the name data for your helper which is reserved. So if you have another helper or an iron router route with data in it, or this template is a subtemplate of another template its likely to redraw too.
Try renaming it to something else.
You can use a closure to find and set the record when the template is rendered and then return a static object in the template helper:
Template.characterSkills.helpers({
dataItem: function(){
return record.get();
}
});
Template.characterSkills.onCreated(function(){
record.set(Session.get("currentCharacterID"));
});
var record = function(){
var local = {};
return {
set: function(characterId){
if ( characterID) local = Characters.findOne({_id: characterID},{fields : { skills : 1 }});
else local = null;
},
get: function(){
return local;
}
}();
Although even this feels like too much work. Surely there's an easier way.
This is a bit puzzling to me. I set data in the router (which I'm using very simply intentionally at this stage of my project), as follows :
Router.route('/groups/:_id',function() {
this.render('groupPage', {
data : function() {
return Groups.findOne({_id : this.params._id});
}
}, { sort : {time: -1} } );
});
The data you would expect, is now available in the template helpers, but if I have a look at 'this' in the rendered function its null
Template.groupPage.rendered = function() {
console.log(this);
};
I'd love to understand why (presuming its an expected result), or If its something I'm doing / not doing that causes this?
From my experience, this isn't uncommon. Below is how I handle it in my routes.
From what I understand, the template gets rendered client-side while the client is subscribing, so the null is actually what data is available.
Once the client recieves data from the subscription (server), it is added to the collection which causes the template to re-render.
Below is the pattern I use for routes. Notice the if(!this.ready()) return;
which handles the no data situation.
Router.route('landing', {
path: '/b/:b/:brandId/:template',
onAfterAction: function() {
if (this.title) document.title = this.title;
},
data: function() {
if(!this.ready()) return;
var brand = Brands.findOne(this.params.brandId);
if (!brand) return false;
this.title = brand.title;
return brand;
},
waitOn: function() {
return [
Meteor.subscribe('landingPageByBrandId', this.params.brandId),
Meteor.subscribe('myProfile'), // For verification
];
},
});
Issue
I was experiencing this myself today. I believe that there is a race condition between the Template.rendered callback and the iron router data function. I have since raised a question as an IronRouter issue on github to deal with the core issue.
In the meantime, workarounds:
Option 1: Wrap your code in a window.setTimeout()
Template.groupPage.rendered = function() {
var data_context = this.data;
window.setTimeout(function() {
console.log(data_context);
}, 100);
};
Option 2: Wrap your code in a this.autorun()
Template.groupPage.rendered = function() {
var data_context = this.data;
this.autorun(function() {
console.log(data_context);
});
};
Note: in this option, the function will run every time that the template's data context changes! The autorun will be destroyed along with the template though, unlike Tracker.autorun calls.
I have a helper
Template.home.helpers({
songId: function(){
return SongManager.getSongId();
},
});
that uses the getSongId method from the SongManager
SongManager = {
init: function(songId){
this.dep = new Tracker.Dependency;
},
getSongId: function(){
this.dep.depend();
return this.songId;
},
setSongId: function(arg){
this.songId = arg;
this.dep.changed();
},
}
But it doesn't cause the template to update reactively when setSongId is called. Am I doing anything wrong? If I use Session.get and Session.set, then everything works properly.
SongManager = {
init: function(songId){
},
getSongId: function(){
return Session.get('songId');
},
setSongId: function(arg){
Session.set('songId', arg);
},
}
I solved it but I'm quite surprised but the results: Placing inside Meteor.startup() doesn't work. I need to investigate why, there's is something about Blaze that I don't know yet.
Check the repo I created for this: https://github.com/mariorodriguespt/stack-song-manager
I am using a new Tracker.Dependency for tracking several things, but it causes the autorun in the code below to run infinitely. What is wrong? The code below is okay once I separate the getSong and getSongId to depend on dep and dep2, instead of just dep.
SongManager = {
dep: new Tracker.Dependency,
dep2: new Tracker.Dependency,
init: function(songId) {
var self = this;
this.setSongId(songId);
Meteor.subscribe('song', songId);
Tracker.autorun(function(){
var songs = self.getSongCursor().fetch();
if (songs.length > 0) {
self.song = songs[0];
self.dep.changed();
}
})
},
getSongCursor: function(){
return Songs.find({_id: this.getSongId()});
},
getSong: function(){
this.dep.depend();
return this.song;
},
getSongId: function(){
this.dep2.depend();
return this.songId;
},
setSongId: function(arg){
this.songId = arg;
this.dep2.changed();
},
};
The problem is that you're creating a circular dependency. I would recommend using ReactiveVar for this rather than working with the lower-level dependency API.
meteor add reactive-var
Then you can just do this:
SongManager = {
song: new ReactiveVar(),
songId: new ReactiveVar(),
init: function(songId) {
this.songId.set(songId);
this.computation = Tracker.autorun(_.bind(this.update, this));
},
update: function() {
var songId = this.songId.get();
Meteor.subscribe('song', songId);
this.song.set(Songs.findOne(songId));
},
stop: function() {
this.computation.stop();
}
};
SongManager.init(oldSongId);
SongManager.songId.set(newSongId);
// After enough time has passed for the subscription to update and tracker to flush:
var currentSong = SongManager.song.get();
console.log(currentSong._id === newSongId); // true
I also added a way for you to stop your autorun computation so it doesn't keep running in the background when it's no longer necessary. Note that since the subscription is run within an autorun, it will automatically be stopped and restarted when the songId changes. The update function will actually be run twice, but Meteor knows not to send two identical subscription requests.
In Facebook react.js, you can compose component within component, or maybe mix and match.
I'm wondering if twitter flight can do the same thing. if so, can anyone gives me an example?
this is what I have so far:
define(function (require) {
var defineComponent = require('flight/lib/component'),
infoInput = require('component/info_input');
return defineComponent(inputSection, infoInput);
function inputSection () {
this.after('initialize', function() {
infoInput.doSomehting();
});
};
});
and my info_input.js is defined below:
define(function (require) {
var defineComponent = require('flight/lib/component');
return defineComponent(infoInput);
function infoInput() {
this.after('initialize', function() {
});
this.doSomething = function() {
alert('I will do something');
};
};
});
This is what mixins are for.
Flight Components are enriched mixins.
From doc/component_api.md
It comes with a set of basic functionality such as event handling and Component registration. Each Component definition mixes in a set of custom properties which describe its behavior.
Read more about Components.
So the answer to your question is Yes.
I guess that what you are doing is legit, although I've never done it before.
I'd rather move the shared logic to a Mixin or attach the two components to the same element and let them talk via events:
component/input_section.js
this.after('initialize', function () {
this.trigger('uiSomethingRequired');
});
component/info_input.js
this.after('initialize', function () {
this.on('uiSomethingRequired', this.doSomething);
});
Solution mentioned by G.G above works!
We may go a step ahead to trigger events on restricted scope instead of document:
component/input_section.js
this.after('initialize', function () {
this.$node.closest(this.attr.parentClass).trigger('uiSomethingRequired');
});
component/info_input.js
this.after('initialize', function () {
this.on(this.$node.closest(this.attr.parentClass), 'uiSomethingRequired', this.doSomething);
});