How to change complicated function to angular directive - javascript

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.

Related

Reactiveness in custom widgets

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.

JointJS element containing HTML button onclick show form

I am showing a form on addDetail buttton inside this element. How can I bind my data to this cell and send it to the server using the toJSon() method?
// Create a custom view for that element that displays an HTML div above it.
// -------------------------------------------------------------------------
joint.shapes.html.ElementView = joint.dia.ElementView.extend({
template: [
'<div class="html-element">',
'<button class="delete">x</button>',
'<label></label>',
'<span></span>', '<br/>',
'<input type="text" name="name" placeholder="name"/>',
'<button class="addDetail">+</button>',
'</div>'
].join(''),
initialize: function () {
_.bindAll(this, 'updateBox');
joint.dia.ElementView.prototype.initialize.apply(this, arguments);
this.$box = $(_.template(this.template)());
// Prevent paper from handling pointerdown.
this.$box.find('input').on('mousedown click', function (evt) {
evt.stopPropagation();
});
// This is an example of reacting on the input change and storing the input data in the cell model.
this.$box.find('input').on('change', _.bind(function (evt) {
alert($(evt.target).val());
this.model.set('input', $(evt.target).val());
}, this));
this.$box.find('.delete').on('click', _.bind(this.model.remove, this.model));
this.$box.find('.addDetail').on('click', _.bind(function (evt) {
addActionDetail();
})
);
// Update the box position whenever the underlying model changes.
this.model.on('change', this.updateBox, this);
// Remove the box when the model gets removed from the graph.
this.model.on('remove', this.removeBox, this);
this.updateBox();
},
render: function () {
joint.dia.ElementView.prototype.render.apply(this, arguments);
this.paper.$el.prepend(this.$box);
this.updateBox();
return this;
},
updateBox: function () {
// Set the position and dimension of the box so that it covers the JointJS element.
var bbox = this.model.getBBox();
// Example of updating the HTML with a data stored in the cell model.
this.$box.find('label').text(this.model.get('label'));
this.$box.find('span').text(this.model.get('select'));
this.$box.css({
width: bbox.width,
height: bbox.height,
left: bbox.x,
top: bbox.y,
transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)'
});
},
removeBox: function (evt) {
this.$box.remove();
}
});
}
In order to save some data on your element you must follow this steps:
Add some elementData propery to the shape model.
Each time the user click on addDetail inside your element you must have the element id, extract the elementData out of it, and then to render the form (you can achieve this by adding custom event listener to your paper)
When clicking the submit form, add add some custom trigger event.
Listen to that triggered event on your graph and try look for the specific cell by the ModelId and update it.
Here is the basic idea example:
1.your shape model:
joint.shapes.myShapes = joint.shapes.myShapes || {};
joint.shapes.myShapes.Element = joint.shapes.basic.Generic.extend({
//basically the defaults function doesn't needed, because the set function adds that automatically.
defaults: _.defaultsDeep({
elementData: null,
}, joint.shapes.basic.Generic.prototype.defaults),
getElementData: function () {
return this.get("elementData");
},
setElementData: function (elementData) {
this.set("elementData", elementData);
},
});
2.On your paper init, add your custom event listener function,
notice that you must have the ModelId to be remembered:
paper.on('addDetail:click', function (cell) {
var elementData = cell.model.getElementData();
elementData.ModelId = cell.model.id;
formRender(elementData);
});
3.trigger some custom event on your submit and the object to be updated within the element model:
function formSubmit() {
graph.trigger('custom:update', newElementData);
}
4.Add some custom event listener to your graph, add call the setElementData by the ModelId:
graph.on('custom:update', function (elementData) {
var cell = graph.getCell(elementData.ModelId);
cell.setElementData(elementData);
}, this);
Now you can send it to the server using the toJSon() method.

Materialize CSS on chip delete

I have been trying to get the tag of a deleted chip from the div in the Materialize chips class, but nothing is working.
Here is what I have already tried.
$('.chips').on('chip.delete', function(e, chip){
console.log(chip);
console.log(e);
console.log(chip.tag);
});
None of the above is working.
With just only console.log(chip), I get undefined error in JavaScript console, but the function is firing when I delete the chip. I am just not able to get the value of tag of deleted chip. I want to store the tag in a variable.
I am creating chips dynamically on Materialize date select:
$('#pm_date').change(function () {
var chipvalue = $(this).val();
if (chipvalue !== "") {
// checking if tag already exits
if ($("#date_chip_select:contains(" + chipvalue + ")").length > 0) {
alert('Date already selected');
} else {
var appendstring = "<div class='chip' id='date_chip_child_" + chip_id + "'>" + chipvalue + "<i class='material-icons close'>close</i></div>";
}
}
});
Here is the fiddle: https://jsfiddle.net/hq22mne4/1/
chips.js, which is part of materialize, doesn't seem to expose any methods for adding or removing chips programmatically. It seems to exclusively listen for an enter keydown event and then internally add the chip.
So, I stitched together a workaround that does just that. I set potential chip's value within your onchange event:
$("#datechips").find('input').val($(this).val());
And create the chip when date picker is closed:
$('.datepicker').pickadate({
selectMonths: true,
selectYears: 15,
onClose: function() {
// add chip via filling the input and simulating enter
$("#datechips").find('input').trigger({ type : 'keydown', which : 13 });
},
});
It may not be ideal, but you should be able to tailor this going forward.
https://jsfiddle.net/j3ej8240/
I've also had a lot of trouble working this out. This is how I capture the add and delete chip events without using jQuery:
function chipDeleted(e, data) {
console.log("Chip was deleted with text: " + data.childNodes[0].textContent);
}
function chipAdded(e, data) {
console.log("Chip was added with text: " + data.childNodes[0].textContent);
}
//
document.addEventListener("DOMContentLoaded", function (e) {
console.log("DOM fully loaded and parsed");
var firstTag = "Initial Tag";
var elems = document.querySelectorAll('.chips');
var instances = M.Chips.init(elems, {
data:[{
tag: firstTag
}],
autocompleteOptions: {
limit: Infinity,
minLength: 1
},
placeholder: "No search...",
onChipDelete: function (e, data) { chipDeleted(e, data) },
onChipAdd: function (e, data) { chipAdded(e, data) }
});
});
And my HTML part is like this:
<body>
<div class="chips search-history"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
</body>

jeditable dynamic select options

I have a page that it using jEditable, and I want to load dynamically the options of a picklist (Depending of the current element).
I have the following example in fiddle:
http://jsfiddle.net/mbv401920150/2rdco6qL/1/
$(document).ready(function() {
$('.edit').editable(function(value, settings) {
console.log(this);
console.log(value);
console.log(settings);
return(value);
}, {
data : " {'E':'E','F':'F','G':'G', 'selected':'F'}", // <---- I WANT TO CHANGE THIS CODE
// ******************************************
// DYNAMIC LOAD - DEPENDING OF THE ELEMENT ID
// ******************************************
// data : function(currentElement) {
// if(currentElement.id == "A") return " { '1':'1', '2':'2', '3':'3' }";
// else return " { 'A':'A', 'B':'B', 'C':'C' }";
// }
type : 'select',
onblur: 'submit'
});
});
I want retrieve the list of specific options depending of the element.
This could be possible?
I figured out how accomplish this task, I include an additional class per each element.
Here is a full solution:
http://jsfiddle.net/mbv401920150/2rdco6qL/3/
$(document).ready(function() {
$('.letter, .number').each(function(i, e) {
$(e).editable(function(value, settings) {
console.log(this);
console.log(value);
console.log(settings);
return (value);
}, {
data: ($(e).hasClass('letter') ?
" { 'A':'A', 'B':'B', 'C':'C' }" :
" { '1':'1', '2':'2', '3':'3' }"),
type: 'select',
onblur: 'submit'
});
});
});
If is a dynamic generation (on mouse over, on click); I remove the auxiliary class after the initialization of jEditable.

How to get parent of first jQuery object?

I have the following code which is run at the end of a long table that has several button.enter-match elements:
$("button.enter-match")
.button()
.on('click', function() {
$("form.enter-match").dialog({
modal: true,
height: 'auto',
width: 200,
close: function() {
$("form.enter-match input[name='code']").val('');
},
open: function() {
$("form.enter-match input[name='code']").val('').focus();
},
buttons: {
Set: function() {
pid = $(this).parents("[data-pid]").data('pid');
if ($("form.enter-match input[name='code']").val() == '') {
alert('Code empty!');
return false;
}
$.post(
request_url,
{
'action': 'set_match',
'pid': pid,
'code': $("form.enter-match input[name='code']").val()
},
function (data) {
error_handler(data);
if (data['error'] == null) {
$("tr#pid-" + pid + " td.code:first div").html('<i>' + $("form.enter-match input[name='code']").val() + '</i>');
$("form.enter-match").dialog('close');
}
},
'json'
);
}
}
});
});
The line pid = $(this).parents("[data-pid]").data('pid'); does not get the pid data value because form#enter_match is created at the very top of the document to be reused in the code as needed. It therefore will not have a parent with the [data-pid] attribute, however, button.enter-match will. How do I get the value in [data-pid] for this particular button.enter-match from within the $("form.enter-match").dialog() portion of the code?
Could not figure out a way to get the next object up, so I simply moved pid = $(this).parents("[data-pid]").data('pid'); to the next scope up under the line .on('click', function() { which solved the problem in all instances.
Thanks to all who pointed out my bad coding practice with regards to IDs and classes. I've updated my coding style to reflect the better principals.

Categories