Skip to content

Commit

Permalink
fix: Restore shiny.bindings.
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubnowicki committed Nov 16, 2023
1 parent 0bc8c72 commit ef2fe58
Show file tree
Hide file tree
Showing 17 changed files with 1,472 additions and 2 deletions.
4 changes: 2 additions & 2 deletions R/shiny.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ NULL
#' @keywords internal
.onLoad <- function(libname, pkgname) { # nolint
# Add directory for static resources
file <- system.file("www", package = "semantic.assets", mustWork = TRUE)
shiny::addResourcePath("semantic.assets", file)
file <- system.file("www", package = "shiny.semantic", mustWork = TRUE)
shiny::addResourcePath("shiny.semantic", file)
}

#' Create universal Shiny input binding
Expand Down
86 changes: 86 additions & 0 deletions inst/www/shiny-custom-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
var customShinyInputBinding = new Shiny.InputBinding();

$.extend(customShinyInputBinding, {

// This initialize input element. It extracts data-value attribute and use that as value.
initialize: function(el) {
var val = $(el).attr('data-value');
$(el).val(val);
},

// This returns a jQuery object with the DOM element.
find: function(scope) {
return $(scope).find('.shiny-custom-input');
},

// Returns the ID of the DOM element.
getId: function(el) {
return el.id;
},

// Given the DOM element for the input, return the value as JSON.
getValue: function(el) {
var value = $(el).val();
var value_type = $(el).attr('data-value-type');
switch (value_type) {
case 'JSON':
return JSON.stringify(value);
case 'text':
return value;
default:
throw new Error("Unrecognized value type of custom shiny input: " + value_type);
}
},

// Given the DOM element for the input, set the value.
setValue: function(el, value) {
el.value = value;
},

// Set up the event listeners so that interactions with the
// input will result in data being sent to server.
// callback is a function that queues data to be sent to
// the server.
subscribe: function(el, callback) {
$(el).on('keyup change', function () { callback(true); });
},

// TODO: Remove the event listeners.
unsubscribe: function(el) {
},

// This returns a full description of the input's state.
getState: function(el) {
return {
value: el.value
};
},

// The input rate limiting policy.
getRatePolicy: function() {
return {
// Can be 'debounce' or 'throttle':
policy: 'debounce',
delay: 500
};
},

receiveMessage: function(el, data) {
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);

if (data.hasOwnProperty('label')) {
var input = $(el).closest(".ui");
if (data.label === "") {
input.dropdown('remove selected')
} else {
input.dropdown('set selected', data.label)
}
}

$(el).trigger('change');
}
});

Shiny.inputBindings.register(customShinyInputBinding, 'shiny.customShinyInput');

18 changes: 18 additions & 0 deletions inst/www/shiny-semantic-DT.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.form-group input {
font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
margin: 0;
outline: 0;
-webkit-appearance: none;
-webkit-tap-highlight-color: rgba(255,255,255,0);
line-height: 1.21428571em;
padding: .67857143em 1em;
font-size: 1em;
background: #fff;
border: 1px solid rgba(34,36,38,.15);
color: rgba(0,0,0,.87);
border-radius: .28571429rem;
-webkit-box-shadow: 0 0 0 0 transparent inset;
box-shadow: 0 0 0 0 transparent inset;
-webkit-transition: color .1s ease,border-color .1s ease;
transition: color .1s ease,border-color .1s ease;
}
60 changes: 60 additions & 0 deletions inst/www/shiny-semantic-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

var semanticButtonBinding = new Shiny.InputBinding();
$.extend(semanticButtonBinding, {
find: function(scope) {
return $(scope).find(".button");
},
getValue: function(el) {
return $(el).data('val') || 0;
},
setValue: function(el, value) {
$(el).data('val', value);
},
getType: function(el) {
return 'shiny.action';
},
subscribe: function(el, callback) {
$(el).on("click.semanticButtonBinding", function(e) {
var $el = $(this);
var val = $el.data('val') || 0;
$el.data('val', val + 1);

callback();
});
},
getState: function(el) {
return { value: this.getValue(el) };
},
receiveMessage: function(el, data) {
var $el = $(el);
// retrieve current label and icon
var label = $el.text();
var icon = '';

// to check (and store) the previous icon, we look for a $el child
// object that has an i tag, and some (any) class (this prevents
// italicized text - which has an i tag but, usually, no class -
// from being mistakenly selected)
if ($el.find('i[class]').length > 0) {
var icon_html = $el.find('i[class]')[0];
if (icon_html === $el.children()[0]) { // another check for robustness
icon = $(icon_html).prop('outerHTML');
}
}

// update the requested properties
if (data.hasOwnProperty('label')) label = data.label;
if (data.hasOwnProperty('icon')) {
icon = data.icon;
// if the user entered icon=character(0), remove the icon
if (icon.length === 0) icon = '';
}

// produce new html
$el.html(icon + ' ' + label);
},
unsubscribe: function(el) {
$(el).off(".semanticButtonBinding");
}
});
Shiny.inputBindings.register(semanticButtonBinding, 'shiny.semanticButton');
112 changes: 112 additions & 0 deletions inst/www/shiny-semantic-calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Convert a number to a string with leading zeros
function padZeros(n, digits) {
var str = n.toString();
while (str.length < digits)
str = "0" + str;
return str;
}

// Given a Date object, return a string in the local time zone.
function formatDate(date) {
if (date instanceof Date) {
return date.getFullYear() + '-' +
padZeros(date.getMonth()+1, 2) + '-' +
padZeros(date.getDate(), 2);
} else {
return null;
}
}

var semanticDateBinding = new Shiny.InputBinding();

$.extend(semanticDateBinding, {

// This initialize input element. It extracts data-value attribute and use that as value.
initialize: function(el) {
$(el).calendar({
onChange: function(date, text, mode) {
$(el).trigger('change');
}
});
},

// This returns a jQuery object with the DOM element.
find: function(scope) {
return $(scope).find('.ss-input-date');
},

getType: function(el) {
return "shiny.date";
},

// Return the date in an unambiguous format, yyyy-mm-dd (as opposed to a
// format like mm/dd/yyyy)
getValue: function(el) {
var date = $(el).calendar('get date');
return formatDate(date);
},

// value must be an unambiguous string like '2001-01-01', or a Date object.
setValue: function(el, value) {
// R's NA, which is null here will remove current value
if (value === null) {
$(el).calendar('clear');
return;
}

$(el).calendar('set date', value);
},

getState: function(el) {
var min = $(el).calendar('get minDate');
var max = $(el).calendar('get maxDate');

// Stringify min and max. If min and max aren't set, they will be
// -Infinity and Infinity; replace these with null.
min = (min === -Infinity) ? null : formatDate(min);
max = (max === Infinity) ? null : formatDate(max);

// startViewMode is stored as a number; convert to string
return {
value: $(el).calendar('get date'),
min: min,
max: max
};
},

subscribe: function(el, callback) {
$(el).on('keyup change ', function(event) { callback(true); });
},

unsubscribe: function(el) {
$(el).off('.semanticDateBinding');
},

getRatePolicy: function() {
return {
policy: 'debounce',
delay: 250
};
},

receiveMessage: function(el, data) {
if (data.hasOwnProperty('min'))
$(el).calendar('set minDate', data.min);
$(el).attr("data-min-date", data.min);

if (data.hasOwnProperty('max'))
$(el).calendar('set maxDate', data.max);
$(el).attr("data-max-date", data.max);

// Must set value only after min and max have been set. If new value is
// outside the bounds of the previous min/max, then the result will be a
// blank input.
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);
$(el).attr("data-date", this.getValue(el));

$(el).trigger('change');
}
});

Shiny.inputBindings.register(semanticDateBinding, 'shiny.semanticDate');
93 changes: 93 additions & 0 deletions inst/www/shiny-semantic-checkbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
var semanticCheckboxBinding = new Shiny.InputBinding();

$.extend(semanticCheckboxBinding, {

// This initialize input element. It extracts data-value attribute and use that as value.
initialize: function(el) {
$(el).checkbox({
fireOnInit: true
});
},

// This returns a jQuery object with the DOM element.
find: function(scope) {
return $(scope).find('.ss-checkbox-input');
},

// Returns the ID of the DOM element.
getId: function(el) {
return el.id;
},

// Given the DOM element for the input, return the value as JSON.
getValue: function(el) {
var checkboxes = $(el).find('.ui.checkbox');
var checkboxCheck = $.map(checkboxes, function(x) { return $(x).checkbox("is checked") });
var checkboxValues = $.map(checkboxes.find('input'), function(n) { return n.value; });
return checkboxValues.filter(function(x) {
return checkboxCheck[checkboxValues.indexOf(x)];
});
},

// Given the DOM element for the input, set the value.
setValue: function(el, value) {
var checkboxes = $(el).find('.ui.checkbox');
checkboxes.checkbox('uncheck');

for (i = 0; i < checkboxes.length; i++) {
if (value.includes($(checkboxes[i]).find('input').attr('value'))) {
$(checkboxes[i]).checkbox('check');
}
}

return null;
},

// Set up the event listeners so that interactions with the
// input will result in data being sent to server.
// callback is a function that queues data to be sent to
// the server.
subscribe: function(el, callback) {
$(el).checkbox({
onChange: function() {
callback();
}
});
},

// TODO: Remove the event listeners.
unsubscribe: function(el) {
$(el).off();
},

receiveMessage: function(el, data) {
if (data.hasOwnProperty('choices')) {
var checkboxClass = $(el).find('.field .checkbox').attr('class');
var checkboxType = $(el).find('.field .checkbox input').attr('type');

$(el).find(".field").remove();

data.choices.forEach(function(x) {
$(el).append(
$(`<div class="field">
<div class="${checkboxClass}">
<input type="${checkboxType}" name="${el.id}" tabindex="0" value="${x.value}" class="hidden">
<label>${x.name}</label>
</div>
</div>`)
);
});

}

if (data.hasOwnProperty("value")) {
this.setValue(el, data.value);
}

if (data.hasOwnProperty("label")) {
$("label[for='" + el.id + "'").html(data.label);
}
}
});

Shiny.inputBindings.register(semanticCheckboxBinding, "shiny.semanticCheckbox");
Loading

0 comments on commit ef2fe58

Please sign in to comment.