Reactiveness in custom widgets - javascript

I need to have a vertical slider input. Since this is not possible with the built-in sliderInput function, I opted to implement it myself.
According to this thread it is either possible to (I) rotate the sliderInput widget using CSS or (II) use a common slider and implement the capability to interact with Shiny.
I decided to go for option (II) because (I) did not work out the way I wanted.
I followed this article in order to implement a custom verticalSlider function
verticalSlider <- function(inputId, min, max, value) {
tagList(
singleton(tags$head(tags$link(rel = "stylesheet", type = "text/css", href = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.8.1/css/bootstrap-slider.min.css"))),
singleton(tags$head(tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.8.1/bootstrap-slider.min.js"))),
singleton(tags$head(tags$link(rel = "stylesheet", type = "text/css", href = "css/verticalSlider.css"))),
singleton(tags$head(tags$script(src = "js/verticalSlider.js"))),
tags$input(id = inputId,
class = "verticalSlider",
type = "text",
value = "",
`data-slider-min` = as.character(min),
`data-slider-max` = as.character(max),
`data-slider-step` = as.character(1),
`data-slider-value` = as.character(min),
`data-slider-orientation` = "vertical"
)
)
}
I implemented the input binding and initialized the slider in "js/verticalSlider.js".
$(function() {
$('.verticalSlider').each(function() {
$(this).slider({
reversed : true,
handle : 'square',
change: function(event, ui){}
});
});
});
var verticalSliderBinding = new Shiny.InputBinding();
$.extend(verticalSliderBinding, {
find: function(scope) {
return $(scope).find(".verticalSlider");
},
getValue: function(el) {
return $(el).value;
},
setValue: function(el, val) {
$(el).value = val;
},
subscribe: function(el, callback) {
$(el).on("change.verticalSliderBinding", function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off(".verticalSliderBinding");
},
getRatePolicy: function() {
return {
policy: 'debounce',
delay: 150
};
}
});
Shiny.inputBindings.register(verticalSliderBinding, "shiny.verticalSlider");
So far so good. The subscribe function is called everytime I move the slider's knob.
Moving the handle has no effect when the slider's value is bound to a textOutput however.
Shiny's "reactiveness" does not seem to work for my custom component. Could someone point me in the right direction?

Hi according to bootstrap-slider readme, you should rewrite getValue and setValue methods in your bindings :
getValue: function(el) {
return $(el).slider('getValue');
},
setValue: function(el, val) {
$(el).slider('setValue', val);
}
I think setValue is only used if you define an update method.

Related

Adding additional data to Kendo UI Context Menu items

I want to utilize the Kendo UI Context Menu in my app. I was expecting the standard behavior of having text displayed in the menu itself but a different value (an ID or key) being returned to the select event handler.
For instance, the menu displays a list of names but when I click on one of them, I get the ID associated with the name.
I've tried adding additional properties besides text to the array of items in the context menu but I don't see them on the event object in the handler.
I can't use the text to find the appropriate id that matches it since there could be entries with the same text but different IDs.
Any ideas?
Edit:
Currently I build the context menu like this:
open: (e) => {
let itemKeys = [1, 2, 3];
let menu = e.sender;
menu.remove(".context-menu-item");
menu.append(itemKeys.map((itemKey) => {
return {
text: "<div data-item-key='" + itemKey + "'>Test Text</div>",
cssClass: "context-menu-item",
encoded: false
};
}));
}
While this solution does satisfy my needs, it adds an extra element to the DOM which, while being insignificant, isn't perfect...
It's undocumented, but ContextMenu actually inherits from Menu. Therefore, all options of Menu are available. In particular, you can add an attr object to your data items, cf the example in the documentation.
To complete your example:
open: (e) => {
let itemKeys = [1, 2, 3];
let menu = e.sender;
menu.remove(".context-menu-item");
menu.append(itemKeys.map((itemKey) => {
return {
text: "Test Text",
cssClass: "context-menu-item",
// add whatever attribute
attr: {
'data-item-key': itemKey
}
};
}));
}
Then, in your select handler:
select: (e) => {
console.log($(e.item).data('item-key'));
}
Option 1)
You might add a data that will specify your Id.
I made this with mvc wrapper but it can be done with pure javascript as well.
#(Html.Kendo()
.ContextMenu()
.Name("contextMenuGridTicketTestiMessaggi")
.Target("#gridTicketTestiMessaggi")
.Filter("tr")
.Orientation(ContextMenuOrientation.Vertical)
.Items(items =>
{
items.Add().Text("Update").HtmlAttributes(new { data_toggle = "update" });
items.Add().Text("Save").HtmlAttributes(new { data_toggle = "save" });
items.Add().Text("Delete").HtmlAttributes(new { data_toggle = "delete" });
})
.Events(e => {
e.Select("contextMenuGridTicketTestiMessaggiSelect");
}));
var contextMenuGridTicketTestiMessaggiSelect = function(e) {
var action = $(e.item).data("toggle");
var that = this;
if (action === "update") {}
...
Option 2) You might define with every item(throught html content) a function to be called in each onClick event for the specific item.
items.Add().Encoded(false).Text("<span onclick='update()'>Update</span>");
items.Add().Encoded(false).Text("<span onclick='delete()'>Delete</span>");
...
Update
<div id="target">Target</div>
<ul id="context-menu"></div>
<script>
$("#context-menu").kendoContextMenu({
target: "#target",
open: function(e) {
let itemKeys = [1, 2, 3];
let menu = e.sender;
menu.remove(".context-menu-item");
menu.setOptions({
dataSource: itemKeys.map((itemKey) => {
return {
text: "<div data-item-key='" + itemKey + "' style='white-space: nowrap'>Test Text</div>",
cssClass: "context-menu-item",
encoded: false
};
})
});
},
select: function(e) {
console.log($($(e.item).find("div")[0]).data("item-key"))
}
});
</script>

How to create custom controls and adding new events to that control using kendo controls?

How to create custom controls in kendo-ui? For example kendo has AutoComplete Control.
Using that I want to create my own "myAutoComplete" with all the events provided by kendo as well as some external events.
The reason is kendo provides very limited events.
For AutoComplete kendo provids (change, close, dataBound, filtering, open,select) but I want to add some events to that like (onKeyPress, onMouseOver etc..).
eg:
My requirement:
$("#autocomplete").myKendoAutoComplete({
change: function(e) {
var value = this.value();
// Use the value of the widget
},
onMouseOver: function() {},
onKeyPress: function() {}
});
Kendo Providing:
$("#autocomplete").kendoAutoComplete({
change: function(e) {
var value = this.value();
// Use the value of the widget
}
});
Please anyone help me to achieve this.
As like jQuery event handling,we can also bind events (like onKeyPress, onMouseOver etc..) to kendo-ui autocomple text-box.
HTML:
<input id="countries" />
JavaScript:
$(document).ready(function () {
var data = ["Paris","Barcelona","Tokyo","New-York","Berck"];
$("#countries").kendoAutoComplete({
dataSource: data,
filter: "startswith",
placeholder: "Select country...",
separator: ", "
})
.keypress(function(e) {
console.log(e);
console.log(e.keyCode);
})
.mouseover(function(e) {
console.log(this.value);
});
});
See this JSFiddle
You can use "Kendo Custom Widgets", than you can create yout own widget with you templates and events.
I did an example , you can use it regarding your needs.
$(function() {
(function($) {
var kendo = window.kendo,
ui = kendo.ui,
Widget = ui.Widget,
var MyKendoAutoComplete = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that._create();
},
options: {
name: "MyKendoAutoComplete",
onMouseOver: function(e) {
alert(e.sender.value());
},
onKeyPress: function(e) {
alert(e.sender.value());
}
},
_create: function() {
var that = this;
// here you will bind your events
kendo.bind(that.element, that.options);
},
_templates: {
//you can create your own templates
}
});
ui.plugin(MyKendoAutoComplete);
})(jQuery);
$('#autocomplete').kendoMyKendoAutoComplete();
});
you can see more here:
http://docs.telerik.com/KENDO-UI/intro/widget-basics/create-custom-kendo-widget
hope this help

How to change complicated function to angular directive

I have a helper function used to bind input with jQuery autocomplete with set of extra options like show bootstrap popover on hover, ability to use secondary label, etc...
I'm facing a problem that my ngModel is not updating when item selected from autocomplete list. This autocomplete select will update text field (for displayed value) and hidden field (for the value). I want to change that to a directive and notify ngModel in text & value fields that item selected.
bindAutocomplete: function (textSelector, valSelector, serviceUrl,
secondaryLabel, minLength, selectCallback,
tooltipPlacement, showOnTopOfField) {
if (!tooltipPlacement) {
tooltipPlacement = "right";
}
var autoCOmpletePosition = { my: "left bottom", at: "left top", collision: "flip" };
if (!showOnTopOfField || showOnTopOfField == false) {
var autoCOmpletePosition = { my: "left top", at: "left bottom", collision: "none" };
}
if (!minLength)
minLength = 2;
var jTextSelector = $(textSelector),
jValSelector = $(valSelector),
jTextSelectorId = jTextSelector.attr("id");
jTextSelector.autocomplete({
source: serviceUrl,
minLength: minLength,
position: autoCOmpletePosition,
select: function (event, ui) {
jValSelector.val(ui.item.id);
if (selectCallback) {
selectCallback(event, ui);
}
},
response: function (event, ui) {
jQuery.data(jTextSelector[0], jTextSelectorId, ui.content); // Store Filtered result in jQuery data store
}
});
if (secondaryLabel) {
var uiAutoCompleteData = jTextSelector.data("ui-autocomplete");
if (uiAutoCompleteData) {
uiAutoCompleteData._renderItem = function (ul, item) {
return $("<li>")
.append("<a><b>" + item.label + "</b><br><span style='font-size:0.8em;'>" + item.desc + "</span></a>")
.appendTo(ul);
};
}
}
jTextSelector.hover(function () { // On Hover
bindPopOver(this);
jTextSelector.popover('show');
},
function () { // On Unhover
jTextSelector.popover('destroy');
});
function bindPopOver(element) {
if (!$(element).val()) {
jValSelector.val("");
jTextSelector.popover('destroy');
}
else {
var listContent = jQuery.data(jTextSelector[0], jTextSelectorId); // Get Filtered result from jQuery data store
var text = jTextSelector.val();
var item = _.find(listContent, function (itemInList) {
return itemInList.label.toLowerCase() == text.toLowerCase();
});
if (item) {
jValSelector.val(item.id);
jTextSelector.popover('destroy');
jTextSelector.popover({
title: text,
trigger: 'hover',
content: item.desc,
placement: tooltipPlacement
});
}
else if (!item && text == "") {
jValSelector.val("");
jTextSelector.popover('destroy');
}
}
};
},
You can use directive techniques to transmit data from/to DOM components and angularjs.
I create a simple code with will help us to make the bind.
plunker
the main rule is the "link" of directive, aided with "scope" definition to reflect external data:
scope: { ngMyModel: '=' }
link: function(scope, el, attrs) {
scope.$watch('ngMyModel', function (val) {
el.val(val);
});
el.on('change input', function() {
scope.$apply(function(scope) {
scope.ngMyModel = el.val();
});
});
}
Where at first part I reflect on input (or another component, like datepicker, etc) any changes on angularjs data.
At second part, I capture events from element and use "scope.$apply" technique to reflect on angularjs data.

Jquery Plugin return On Selection/Click

On getting no answer on my previous question , I decided to go on with this plugin structure.
(function ( $ ) {
$.fn.myPlugin = function (options) {
options = $.extend( {}, $.fn.myPlugin.defaults, options );
return this.each(function (i, el) {
("someiD").live("click",function() {
UpdateCounter();
});
});
};
$.fn.myPlugin.defaults = {
///options here for overiding
};
}(jQuery));
I have made a plugin in which I have to select a button to increase a counter and then I don't know how to get the updated values of the counter that is OnSelect/OnClick of the button.... Can anyone give me any insights on how should I be dealing with this without changing my plugin structure?
Basically like this:
(function ( $ ) {
$.fn.myPlugin = function (options) {
options = $.extend( {}, $.fn.myPlugin.defaults, options );
// $(this) is your selected element, #someSelect in this case
$(this).on('select', function(e) {
console.log('selected!')
})
};
// execute your plugin on the selected element
$('#someSelect').myPlugin();

Adjust max on all jQuery UI spinners after one changes?

I'm using the beta version of the jQuery UI spinner, as of jQuery UI 1.9 milestone 7. What I'm trying to do is the following: I have a series of spinner inputs, all with the same class that I can use as a jQuery selector (.spinner). These spinners are setting values for a set of objects, which all draw from the same "pool" of resources. Each time a spinner's value is updated, I want to go through and update all spinners' max value based on how much of that resource pool remains. Here is the basic code so far:
var unusedPool = 500;
jQuery(document).ready(function($) {
$('.spinner').spinner({
min: 0,
max: (unusedPool + this.value),
change: function(event, ui) {
// An AJAX call which pushes the change to the DB,
// and returns a JSON object containing the change in what remains
// in the resource pool.
jQuery.ajax({
// URL, type, etc. etc.
data: { newValue: this.value },
success: function (data) {
unusedPool -= data.change;
// Here is where I need to go through and update *all* spinners,
// so their new "max" is their current value plus the new
// unusedPoolSize.
}
}
}
});
Is there a way to do what I'm trying to do? Do I need to bind an event to all spinners and then trigger that event, or is there a more direct way (within the change callback) to access all of the spinners and alter the "max" value?
I suppose you could add something like this in the success function:
$('.spinner').each(function() {
$(this).spinner().change(function() {
$(this).spinner('option', 'max', $(this).spinner('value') + unusedPool);
});
});
Kvam's answer turns out to be very close, but the update inside the success function does not go inside of the spinner.change callback (since the .ajax call itself is inside the .change callback). I eventually discovered that this does the job:
var unusedPool = 500;
jQuery(document).ready(function($) {
var spinners = $('.spinner').each(function() {
$(this).spinner({
min: 0,
max: (parseInt(this.value) + unusedPool),
change: function(event, ui) {
jQuery.ajax({
data: { newValue = this.value },
success: function(data) {
unusedPool -= data.change;
spinners.each(function() {
var newMax = parseInt(this.value) + unusedPool;
$(this).spinner({ max: newMax });
}
}
});
}
});
});

Categories