Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select2 4.0 support wip #30

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 46 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# angular-select2

> [select2](http://ivaynberg.github.io/select2/) bindings for Angular.JS
> [select2](https://select2.github.io/) bindings for Angular.JS

## Installation
Add angular-select2 to your project:
Expand All @@ -22,52 +22,78 @@ angular.module('myApp', ['rt.select2']);
```

## Usage
Usage a similar to a normal select with `ngOptions`:

A JS Bin demo showing working usage examples are available [here](https://jsbin.com/fifaqu/edit?html,js,output).

Usage similar to a normal select:

```html
<select2 ng-model="obj.field" s2-options="val.id as val.name for val in values"></select2>
<select2 ng-model="selected" ng-options="val.id as val.name for val in values" options="{placeholder:'Select an option', allowClear: true}">
</select2>
```

*Note: using `ng-options` was supported until Angular 1.4 made this impossible. When upgrading to Angular.JS 1.4, be sure to replace all instances of `ng-options` to `s2-options`.*

In fact, you can replace any `<select>` tag by a `<select2>` tag and it should just work.

A multi-selection works similarly: add a `multiple` attribute.

You can set any [select2](http://ivaynberg.github.io/select2/) option by passing an options object:

```html
<select2 ng-model="obj.field" s2-options="val.id as val.name for val in values" options="{ allowClear: true }"></select2>
<select2 ng-model="selected" options="{ allowClear: true }">
...
</select2>
```

## Async loading of data
Async-loaded data can by used by leaving out the `s2-options` attribute and by specifying a `query` function:
Async-loaded data can by used by specifying an `ajax` configuration. The following example is adapted from the "Loading remote data" example on [https://select2.github.io/examples.html](https://select2.github.io/examples.html).

```js
angular.module('myApp').controller('MyController', function ($scope) {
angular.module('myApp').controller('MyAsyncController', function ($scope) {

/* ... omitted for brevity, see the JS bin ... */

$scope.selected = "3620194";

$scope.queryOptions = {
query: function (query) {
var data = {
results: [
{ id: "1", text: "A" },
{ id: "2", text: "B" }
]
};

query.callback(data);
}
ajax: {
url: "https://api.github.com/search/repositories",
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term, // search term
page: params.page
};
},
processResults: function (data, page) {
// parse the results into the format expected by Select2.
// since we are using custom formatting functions we do not need to
// alter the remote JSON data
return {
results: data.items
};
},
cache: true
},
escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
minimumInputLength: 1,
templateResult: formatRepo, // omitted for brevity
templateSelection: formatRepoSelection // omitted for brevity
};
});
```

```html
<select2 ng-model="values.query" options="queryOptions"></select2>
<select2 ng-model="selected" options="queryOptions">
<option value=""></option>
<option value="3620194">select2/select2</option>
</select2>
```

## Custom formatting, restrictions, tokenization, ...
This directive is just simple glue to the underlying select2.

[Check the select2 documentation for an overview of the full capabilities.](http://ivaynberg.github.io/select2/)
[Check the select2 documentation for an overview of the full capabilities.](https://select2.github.io/examples.html)

## Configuring global defaults
You can set a default for any option value by using `select2Config`:
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
],
"dependencies": {
"angular": "~1.4.0",
"select2": "~3.5.2"
"select2": "~4.0.0"
},
"devDependencies": {
"angular-mocks": "~1.4.0"
Expand Down
234 changes: 20 additions & 214 deletions dist/angular-select2.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,25 @@ angular.module("rt.select2", [])
.directive("select2", ["$rootScope", "$timeout", "$parse", "$filter", "select2Config", "select2Stack", function ($rootScope, $timeout, $parse, $filter, select2Config, select2Stack) {
"use strict";

var filter = $filter("filter");

function sortedKeys(obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys.sort();
}

var defaultOptions = {};
//0000111110000000000022220000000000000000000000333300000000000000444444444444444000000000555555555555555000000066666666666666600000000000000007777000000000000000000088888
var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;

if (select2Config) {
angular.extend(defaultOptions, select2Config);
}

return {
require: "ngModel",
scope: {
ngModel: "=",
options: "="
},
priority: 1,
restrict: "E",
template: "<input type=\"hidden\"></input>",
transclude: true, // transclusion instructs angular to embed the original content from the DOM into the resultant output
template: "<select ng-transclude></select>",
replace: true,
link: function (scope, element, attrs, controller) {
var getOptions;

var opts = angular.extend({}, defaultOptions, scope.$eval(attrs.options));
var opts = angular.extend({}, defaultOptions, scope.options);
var isMultiple = angular.isDefined(attrs.multiple) || opts.multiple;

opts.multiple = isMultiple;
Expand All @@ -61,178 +50,6 @@ angular.module("rt.select2", [])
opts.placeholder = attrs.placeholder;
}

var filterOptions = $parse(attrs.optionsFilter);

// All values returned from Select2 are strings. This is a
// problem if you supply integer indexes: they'll become
// strings once passing through this directive. We keep a
// mapping between string keys and values through the
// optionItems object, to be able to return the correctly typed
// value.
var optionItems = {};

function filterValues(values) {
if (filterOptions) {
var filterParams = filterOptions(scope);
if (filterParams) {
return filter(values, filterParams);
}
}

return values;
}

if (attrs.s2Options) {
var match;
if (!(match = attrs.s2Options.match(NG_OPTIONS_REGEXP))) {
throw new Error("Invalid s2Options encountered!");
}

var displayFn = $parse(match[2] || match[1]);
var valuesFn = $parse(match[7]);
var valueName = match[4] || match[6];
var valueFn = $parse(match[2] ? match[1] : valueName);
var keyName = match[5];

getOptions = function (callback) {
optionItems = {};
var values = filterValues(valuesFn(scope));
var keys = (keyName ? sortedKeys(values) : values) || [];

var options = [];
for (var i = 0; i < keys.length; i++) {
var locals = {};
var key = i;
if (keyName) {
key = keys[i];
locals[keyName] = key;
}
locals[valueName] = values[key];

var value = valueFn(scope, locals);
var label = displayFn(scope, locals) || "";

// Select2 returns strings, we use a dictionary to get
// back to the original value.
optionItems[value] = {
id: value,
text: label,
obj: values[key]
};

options.push(optionItems[value]);
}

callback(options);
};

opts.query = function (query) {
var values = filterValues(valuesFn(scope));
var keys = (keyName ? sortedKeys(values) : values) || [];

var options = [];
for (var i = 0; i < keys.length; i++) {
var locals = {};
var key = i;
if (keyName) {
key = keys[i];
locals[keyName] = key;
}
locals[valueName] = values[key];

var value = valueFn(scope, locals);
var label = displayFn(scope, locals) || "";

if (label.toLowerCase().indexOf(query.term.toLowerCase()) > -1) {
options.push({
id: value,
text: label,
obj: values[key]
});
}
}

query.callback({
results: options
});
};

// Make sure changes to the options get filled in
scope.$watch(match[7], function () {
controller.$render();
});
} else {
if (!opts.query) {
throw new Error("You need to supply a query function!");
}

var queryFn = opts.query;
opts.query = function (query) {
var cb = query.callback;
query.callback = function (data) {
for (var i = 0; i < data.results.length; i++) {
var result = data.results[i];
optionItems[result.id] = result;
}
cb(data);
};
queryFn(query);
};

getOptions = function (callback) {
opts.query({
term: "",
callback: function (query) {
callback(query.results);
}
});
};
}

function getSelection(callback) {
if (isMultiple) {
getOptions(function (options) {
var selection = [];
for (var i = 0; i < options.length; i++) {
var option = options[i];
var viewValue = controller.$viewValue || [];
if (viewValue.indexOf(option.id) > -1) {
selection.push(option);
}
}
callback(selection);
});
} else {
getOptions(function () {
callback(optionItems[controller.$viewValue] || { obj: {} });
});
}
}

controller.$render = function () {
getSelection(function (selection) {
if (isMultiple) {
element.select2("data", selection);
} else {
element.select2("val", selection.id);
}
});
};

if (!opts.initSelection) {
opts.initSelection = function (element, callback) {
getSelection(callback);
};
} else {
var _initSelection = opts.initSelection;
opts.initSelection = function (element, callback) {
_initSelection(element, function (result) {
optionItems[result.id] = result;
callback(result);
});
};
}

// register with the select2stack
var controlObj = {
close: function () {
Expand All @@ -244,40 +61,29 @@ angular.module("rt.select2", [])
select2Stack.$unregister(controlObj);
});

// initiate select2
$timeout(function () {
element.select2(opts);
element.on("change", function (e) {
scope.$apply(function () {
var val;
if (isMultiple) {
var vals = [];
for (var i = 0; i < e.val.length; i++) {
val = optionItems[e.val[i]];
if (val) {
vals.push(val.id);
}
}
controller.$setViewValue(vals);
} else {
val = optionItems[e.val];
controller.$setViewValue(val ? val.id : null);
}

controller.$render();

});
});

element.on("select2-blur", function () {
if (controller.$touched) {
return;
}

scope.$apply(controller.$setTouched);
controller.$setTouched();
});

controller.$render();
});

// make sure that changes to the value is reflected in the select2 input
scope.$watch("ngModel", function (newVal, oldVal) {
if (newVal === oldVal) {
return;
}
$timeout(function () {
element.trigger("change");
});
});

}
};
}]);
2 changes: 1 addition & 1 deletion dist/angular-select2.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading