Skip to content
This repository has been archived by the owner on Jan 28, 2020. It is now read-only.

Commit

Permalink
Merge pull request #461 from mitodl/refactor/gs/select2
Browse files Browse the repository at this point in the history
Implemented Select2 React class
  • Loading branch information
noisecapella committed Aug 5, 2015
2 parents 869c145 + d16a770 commit 190a00a
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 44 deletions.
102 changes: 102 additions & 0 deletions ui/jstests/test-select2.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
define(['QUnit', 'jquery', 'utils', 'reactaddons',
'test_utils'], function(
QUnit, $, Utils, React, TestUtils) {
'use strict';

var Select2 = Utils.Select2;
var options = [
{id: 'one', text: 'One'},
{id: 'two', text: 'Two'},
{id: 'three', text: 'Three'}
];

QUnit.module('Tests for Select2', {
beforeEach: function () {
TestUtils.setup();

},
afterEach: function() {
TestUtils.cleanup();
}
});

QUnit.test('Test Select2 defaults', function(assert) {
var done = assert.async();
var afterMount = function(component) {
var node = React.findDOMNode(component);
var $select = $(node).find("select");

assert.equal($select.length, 1);
assert.equal($select.val(), null);

done();
};

React.addons.TestUtils.renderIntoDocument(
<Select2 ref={afterMount} />
);
});

QUnit.test('Assert onChange handler', function(assert) {
var done = assert.async();

var onChange = function(e) {
assert.equal(e.target.value, "two");
done();
};

var afterMount = function(component) {
var select = $(React.findDOMNode(component)).find("select")[0];
$(select).val('two').trigger('change');
};

React.addons.TestUtils.renderIntoDocument(
<Select2 ref={afterMount}
options={options}
onChange={onChange} />
);
});

QUnit.test('Assert onChange handler for multi select', function(assert) {
var done = assert.async();

var onChange = function(e) {
assert.equal($(e.target).val(), "two,three");
done();
};

var afterMount = function(component) {
var select = $(React.findDOMNode(component)).find("select")[0];
$(select).val(['two', 'three']).trigger('change');
};

React.addons.TestUtils.renderIntoDocument(
<Select2 ref={afterMount}
options={options}
multiple={true}
onChange={onChange} />
);
});

QUnit.test('Assert onChange handler for deselect', function(assert) {
var done = assert.async();
var onChange = function(e) {
assert.equal(e.target.value, "two");
done();
};

var afterMount = function(component) {
var select = $(React.findDOMNode(component)).find("select")[0];
$(select).val(['two']).trigger('change');
};

React.addons.TestUtils.renderIntoDocument(
<Select2 ref={afterMount}
options={options}
multiple={true}
values={['one', 'two']}
onChange={onChange} />
);
});
}
);
57 changes: 18 additions & 39 deletions ui/static/ui/js/learning_resources.jsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,42 @@
define('learning_resources', [
'reactaddons', 'jquery', 'lodash', 'utils', 'select2'], function (
'reactaddons', 'jquery', 'lodash', 'utils'], function (
React, $, _, Utils) {
'use strict';

var StatusBox = Utils.StatusBox;
var Select2 = Utils.Select2;

var VocabularyOption = React.createClass({
applySelect2: function () {
//this function can be used only if the component has been mounted
render: function () {
var options = _.map(this.props.terms, function (term) {
return {
id: term.slug,
text: term.label
};
});
var thiz = this;
var isMultiTerms = this.props.vocabulary.multi_terms;
//not allowing the clear for the multi terms dropdown
var allowClear = !isMultiTerms;
//apply select2 to the right elements
$(React.findDOMNode(this)).find('select').select2({
multiple: isMultiTerms,
data: options,
placeholder: "Select a value for " + this.props.vocabulary.name,
allowClear: allowClear,
theme: "bootstrap",
})
.val(this.props.selectedTerms)
.trigger('change')
.on('change', function () {
var selectValues = [].concat($(this).val());
//if there are single select dropdowns, .val() can return null
selectValues = _.filter(selectValues, function(value) {
return value !== null;
});
thiz.props.updateTerms(thiz.props.vocabulary.slug, selectValues);
});
},
render: function () {

return <div className="form-group">
<label className="col-sm-6 control-label">
{this.props.vocabulary.name}
</label>
<div className="col-sm-6">
<select className="form-control">
</select>
<Select2
className="form-control"
placeholder={"Select a value for " + this.props.vocabulary.name}
options={options}
onChange={this.handleChange}
values={this.props.selectedTerms}
multiple={this.props.vocabulary.multi_terms}
/>
</div>
</div>;
},
componentDidMount: function() {
this.applySelect2();
},
componentDidChange: function() {
this.applySelect2();
},
componentWillChange: function() {
//before react applies the changes need to detach the event handler
//from the select node
$(React.findDOMNode(this)).find('select').off('change');
handleChange: function(e) {
var selectValues = _.pluck(
_.filter(e.target.options, function(option) {
return option.selected && option.value !== null;
}), 'value');
this.props.updateTerms(this.props.vocabulary.slug, selectValues);
}
});

Expand Down
89 changes: 84 additions & 5 deletions ui/static/ui/js/utils.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
define("utils", ["jquery", "lodash", "reactaddons"], function ($, _, React) {
define("utils", ["jquery", "lodash", "reactaddons", "select2"],
function ($, _, React) {
'use strict';

/**
Expand Down Expand Up @@ -59,10 +60,9 @@ define("utils", ["jquery", "lodash", "reactaddons"], function ($, _, React) {
// onChange is set here to silence React warnings. We're using
// ifToggled instead.
render: function() {
return <span><input type="checkbox"
value={this.props.value}
checked={this.props.checked}
return <span><input {...this.props}
onChange={this.handleChange}
type="checkbox"
/></span>;
},
handleChange: function(e) {
Expand Down Expand Up @@ -91,6 +91,84 @@ define("utils", ["jquery", "lodash", "reactaddons"], function ($, _, React) {
}
});

/**
* Component for Select2 select elements.
*/
var Select2 = React.createClass({
render: function() {
return <span>
<select {...this.props} onChange={function() {}} />
</span>;
},
handleChange: function(e) {
if (this.props.onChange !== undefined) {
this.props.onChange(e);
}
},
applySelect2: function () {
//this function can be used only if the component has been mounted
var thiz = this;
var isMultiTerms = this.props.multiple;
//not allowing the clear for the multi terms dropdown
var allowClear = !isMultiTerms;
//apply select2 to the right elements
var parent = React.findDOMNode(this);
var $select = $(parent).find('select').first();

// HACK to position dropdown below select in DOM so that
// it won't attach event handlers all the way up and prevent
// correct operation of the scrollbar.
var Select2Utils = $.fn.select2.amd.require("select2/utils");
var DropdownAdapter = $.fn.select2.amd.require("select2/dropdown");
var AttachContainer = $.fn.select2.amd.require(
"select2/dropdown/attachContainer");
var DropdownSearch = $.fn.select2.amd.require(
"select2/dropdown/search");
var CustomAdapter = Select2Utils.Decorate(
Select2Utils.Decorate(DropdownAdapter, DropdownSearch),
AttachContainer
);

$select.select2({
multiple: isMultiTerms,
data: this.props.options,
placeholder: this.props.placeholder,
allowClear: allowClear,
theme: "bootstrap",
dropdownAdapter: CustomAdapter
}).val(this.props.values)
.trigger('change')
.on('change', function (e) {
thiz.handleChange(e);
});
},
componentDidMount: function() {
this.applySelect2();
},
componentDidUpdate: function() {
this.applySelect2();
},
componentWillUpdate: function() {
var node = React.findDOMNode(this);
var $select = $(node).find('select').first();
$select.off('change');

var select2 = $select.data('select2');
// HACK: workaround to prevent unselect and related events
// from executing.
select2.$container.removeClass('select2-container--open');
if (select2 !== null && select2 !== undefined) {
// HACK: workaround to prevent toggleDropdown from executing on
// destroyed object.
select2.options.set('disabled', true);
select2.destroy();
}

// The select2 widget should get recreated in applySelect2 so
// it shouldn't matter that we just made these modifications.
}
});

return {
getCollection: _getCollection,
/**
Expand All @@ -115,6 +193,7 @@ define("utils", ["jquery", "lodash", "reactaddons"], function ($, _, React) {
});
},
StatusBox: StatusBox,
ICheckbox: ICheckbox
ICheckbox: ICheckbox,
Select2: Select2
};
});

0 comments on commit 190a00a

Please sign in to comment.