React component + jQuery draggable thinks every element is the same object - jquery-ui

Disclaimer: React noob over here - I apologize if I'm bastardizing the way it's meant to be used.
I'm trying to use jQuery's draggable API in conjunction with React components. However, whenever I drag the list item, the object associated to the drag event is the same.
class CompanyListItem extends React.Component {
componentDidMount() {
that = this
$("[role='draggable']").draggable({
start: (e, ui)=> {
that.props.dragHandler(event, ui, this);
},
revert: true
});
}
render() {
return (
<li className="draggable company" id={this.props.company.id} role="draggable">
<span>{this.props.company.name}</span>
</li>
);
}
}
class CompanyList extends React.Component {
render() {
var rows = []
_.each(this.props.companies, (company) => {
rows.push(<CompanyListItem company={company} dragHandler={this.props.dragHandler} key={company.id}/>);
});
return (
<ul> Select
{rows}
</ul>
);
}
}
For example, the component rendered below:
The object tied to both list elements is the "HopShop" (the first rendered list item)

Everytime you call $("[role='draggable']").draggable(...) you reset the other components. You should call it on the id instead.
componentDidMount() {
that = this
$("#" + this.props.company.id).draggable({
start: (e, ui)=> {
that.props.dragHandler(event, ui, this);
},
revert: true
});
}

Related

jquery_ui menu ui parameter for dynamic sub-menu

I am trying to create a dynamic sub-menu. For example, the top-level items are load and edit. Whenever the menu is focused, a get JSON is called and the result used to populate the possible items to load under the load menu item. However, the ui parameter for the select event handler no longer seems to correspond to the item clicked and calling ui.item.text() will instead return the entire text to the sub-menu.
HTML:
...
<ul id="menu">
<li><div>load</div><ul id="load"></ul></li>
<li><div>edit</div></li>
</ul>
...
javascript:
var load_populate = function ( json ) {
var ul = $( "#load" );
ul.empty();
for ( let item of json ) {
ul.append ( $( "<li>" ).append ( $( "<div>" ).text ( item.name ) ) );
}
};
var menu_focus = function ( event, ui ) {
$.getJSON ( load_options_url )
.done ( load_populate );
};
var menu_select = function ( event, ui ) {
console.log ( ui.item.text() );
};
$( "#menu" ).menu ({
"focus" : menu_focus,
"select" : menu_select
});
Clicking an item in the load sub-menu logs
loaditem1item2item3, etc.
Refreshing the menu did not work.
How do I go about this?
Here is an example based on your code.
Example Fiddle
This uses POST just to simulate the AJAX activity
https://jsfiddle.net/Twisty/qpky9ht1/53/
HTML
<ul id="menu">
<li>
<div>load</div>
<ul id="load">
</ul>
</li>
<li>
<div>edit</div>
</li>
</ul>
JavaScript
Adjusted to be more similar to your code.
$(function() {
function makeListItem(o, n) {
$(o).append("<li><div>" + n + "</div></li>");
}
$("#menu").menu({
focus: function(e, ui) {
console.log("FOCUS", e, ui.item);
if (ui.item.parent().is("#menu")) {
$.getJSON (load_options_url,
function(json) {
console.log("LOAD");
$("#load").empty();
$.each(json, function(i, m) {
console.log("ITEM", i, m);
makeListItem("#load", m.name);
});
$("#menu").menu("refresh");
});
}
},
select: function(e, ui) {
console.log("SELECT", ui.item);
}
});
});
This will gather the list data in focus and append them. Once this is done, refresh method is executed on the Menu. This will assimilate all the new list items into the menu. Since focus can be executed on all menu items, you want to ensure you are not rebuilding the menu while focusing on sub-menu items. Hence the if statement.

listview disappeared after setState({ds})

I am confused with the rerender mechanism of listview.
Page 1 have render a listview with two item, then I click 'Add' button, navigate to another page, and add one item to Page 1's datasource, then navigator back.
What I expect to see is Page 1 with three item, but actually is Page 2, listview is disappeared. But if I use mouse/finger touch it, listView come out again with three item.
I have test it on my iphone and simulator
Page 1's source code:
class Market extends Component {
constructor(props) {
super(props)
this.state = {
dataSource: new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }),
}
}
componentDidMount() {
this.refreshListView(this.props.data)
}
componentWillReceiveProps(nextProps) {
this.refreshListView(nextProps.data)
}
refreshListView() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data)
})
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
refreshControl={
<RefreshControl/>
}
/>
)
}
const mapStateToProps = createSelector(
selectData(),
(data) => ({
data,
})
)
export default connect(mapStateToProps)(Market)
Just found something that has fixed mine! try adding removeClippedSubviews={false} to your ListView
https://github.com/facebook/react-native/issues/8607#issuecomment-231371923

React Bootstrap OverlayTrigger with trigger="focus" bug work around

In iOS safari, OverlayTrigger with trigger="focus" isn't able to dismiss when tapping outside. Here is my code:
<OverlayTrigger
trigger="focus"
placement="right"
overlay={ <Popover id="popoverID" title="Popover Title">
What a popover...
</Popover> } >
<a bsStyle="default" className="btn btn-default btn-circle" role="Button" tabIndex={18}>
<div className="btn-circle-text">?</div>
</a>
</OverlayTrigger>
I know that this is a known bug for Bootstrap cuz this doesn't even work on their own website in iOS, but does anyone know any method to go around it? It would be the best if it is something that doesn't require jQuery, but jQuery solution is welcome. Thanks.
OK, since no one else gives me a work around, I worked on this problem with my co-worker together for 3 days, and we came up with this heavy solution:
THE PROBLEM:
With trigger="focus", Bootstrap Popover/Tooltip can be dismissed when CLICKING outside the Popover/Tooltip, but not TOUCHING. Android browsers apparently changes touches to clicks automatically, so things are fine on Android. But iOS safari and browsers that is based on iOS safari (iOS chrome, iOS firefox, etc...) don't do that.
THE FIX:
We found out that in React Bootstrap, the Overlay component actually lets you customize when to show the Popover/Tooltip, so we built this component InfoOverlay based on Overlay. And to handle clicking outside the component, we need to add event listeners for both the Popover/Tooltip and window to handle both 'mousedown' and 'touchstart'. Also, this method would make the Popover have its smallest width all the time because of the padding-right of the component is initially 0px, and we make based on the width of some parent component so that it is responsive based on the parent component. And the code looks like this:
import React, { Component, PropTypes as PT } from 'react';
import {Popover, Overlay} from 'react-bootstrap';
export default class InfoOverlay extends Component {
static propTypes = {
PopoverId: PT.string,
PopoverTitle: PT.string,
PopoverContent: PT.node,
// You need to add this prop and pass it some numbers
// if you need to customize the arrowOffsetTop, it's sketchy...
arrowOffsetTop: PT.number,
// This is to be able to select the parent component
componentId: PT.string
}
constructor(props) {
super(props);
this.state = {
showPopover: false,
popoverClicked: false
};
}
componentDidMount() {
// Here are the event listeners and an algorithm
// so that clicking popover would not dismiss itself
const popover = document.getElementById('popoverTrigger');
if (popover) {
popover.addEventListener('mousedown', () => {
this.setState({
popoverClicked: true
});
});
popover.addEventListener('touchstart', () => {
this.setState({
popoverClicked: true
});
});
}
window.addEventListener('mousedown', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
window.addEventListener('touchstart', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
// this is to resize padding-right when window resizes
window.onresize = ()=>{
this.setState({});
};
}
// This function sets the style and more importantly, padding-right
getStyle() {
if (document.getElementById(this.props.componentId) && document.getElementById('popoverTrigger')) {
const offsetRight = document.getElementById(this.props.componentId).offsetWidth - document.getElementById('popoverTrigger').offsetLeft - 15;
return (
{display: 'inline-block', position: 'absolute', 'paddingRight': offsetRight + 'px'}
);
}
return (
{display: 'inline-block', position: 'absolute'}
);
}
overlayOnClick() {
this.setState({
showPopover: !(this.state.showPopover)
});
}
render() {
const customPopover = (props) => {
return (
{/* The reason why Popover is wrapped by another
invisible Popover is so that we can customize
the arrowOffsetTop, it's sketchy... */}
<div id="customPopover">
<Popover style={{'visibility': 'hidden', 'width': '100%'}}>
<Popover {...props} arrowOffsetTop={props.arrowOffsetTop + 30} id={this.props.PopoverId} title={this.props.PopoverTitle} style={{'marginLeft': '25px', 'marginTop': '-25px', 'visibility': 'visible'}}>
{this.props.PopoverContent}
</Popover>
</Popover>
</div>
);
};
return (
<div id="popoverTrigger" style={this.getStyle()}>
<a bsStyle="default" className="btn btn-default btn-circle" onClick={this.overlayOnClick.bind(this)} role="Button" tabIndex={13}>
<div id="info-button" className="btn-circle-text">?</div>
</a>
<Overlay
show={this.state.showPopover}
placement="right"
onHide={()=>{this.setState({showPopover: false});}}
container={this}>
{customPopover(this.props)}
</Overlay>
</div>
);
}
}
In the end, this is a heavy work around because it is a big amount of code for a fix, and you can probably feel your site is slowed down by a tiny bit because of the 4 event listeners. And the best solution is just tell Bootstrap to fix this problem...

jqueryui Tabs with Tablesorter

I'm using jquery ui tabs with the tablesorter 2.0 plugin to obtain sort abilities on a dynamically populated html table but the sort only happens on the first tab upon page load. The other tabs do not sort or obtain the zebra striping form the tablesorter.
html:
<div id="tabs">
<ul>
<li>Ftp Only</li>
<li>Billing Only</li>
<li>Variance</li>
<li>Adj Only</li>
</ul>
</div>
I've tried:
$('#tabs').tabs({
ajaxOptions: {cache: false},
load: function()
{
$("#ReportTable").tablesorter();
}
});
Any suggestions are much appreciated.
The zebra widget only applies to visible rows, so you'll need to trigger the applyWIdgets method. And I'm going to assume you are using jQuery UI 1.10.2 and jQuery 2.0, where you can use the activate callback (demo):
$("#tabs").tabs({
activate: function (event, ui) {
var $t = ui.newPanel.find('table');
// make sure there is a table in the tab
if ($t.length) {
if ($t[0].config) {
// update zebra widget
$t.trigger('applyWidgets');
} else {
// initialize tablesorter
$t.tablesorter({
theme: 'blue',
widgets: ["zebra"]
});
}
}
}
});
Update: Oops, if the table is in the first tab, use this code (demo):
var tablesorterOptions = {
theme: 'blue',
widgets: ["zebra"]
};
$("#tabs").tabs({
create: function (event, ui) {
var $t = ui.panel.find('table');
if ($t.length) {
$t.tablesorter(tablesorterOptions);
}
},
activate: function (event, ui) {
var $t = ui.newPanel.find('table');
if ($t.length) {
if ($t[0].config) {
$t.trigger('applyWidgets');
} else {
$t.tablesorter(tablesorterOptions);
}
}
}
});

jQueryUI Autocomplete-Widget: I want to bind a function to the select event of the menu widget

I have the following script using the jQueryUI autocomplete widget. It calls some function whenever a menu item in the selection box is being selected:
<script type="text/javascript">
$(function() {
$( "#tags" ).autocomplete({
source: [ "ActionScript", "AppleScript", "Asp"],
select: function() {
console.log("Element has been selected");
}
});
});
</script>
<div class="ui-widget">
<label for="tags">Tags: </label>
<input id="tags">
</div>
This works nicely. But I need this method in a multiple of instances of the autocomplete widget, so I prefer extending the autocomplete widget using the widget factory.
This works nicely whenever I want to override methods of the autocomplete plugin:
$.widget("ui.myAutocomplete", $.extend({}, $.ui.autocomplete.prototype, {
search: function( value, event ) {
// this WORKS!
console.log('overriding autocomplete.search')
return $.ui.autocomplete.prototype.search.apply(this, arguments);
}
}));
However, I have no clue how to do that for the underlying menu widget.
I tried to override the _init method and binding a function to the select event. However this does not work as I don't know how to access the bind method of the menu-widget (or this menu widget is not yet there at this point during runtime)
$.widget("ui.myAutocomplete", $.extend({}, $.ui.autocomplete.prototype, {
_init: function() {
// this does NOT work.
this.bind('select', function() { console.log('item has been selected') })
return $.ui.autocomplete.prototype._init.apply(this, arguments);
}
}));
I think you're close; you should be overriding _create instead of _init:
$.widget("ui.myAutocomplete", $.extend({}, $.ui.autocomplete.prototype, {
_create: function() {
// call autocomplete's default create method:
$.ui.autocomplete.prototype._create.apply(this, arguments);
// this.element is the element the widget was invoked with
this.element.bind("autocompleteselect", this._select);
},
_select: function(event, ui) {
// Code to be executed upon every select.
}
}));
Usage:
$("input").myAutocomplete({
/* snip */
});
Here's a working example: http://jsfiddle.net/EWsS4/

Resources