Skip to content
This repository has been archived by the owner on May 21, 2021. It is now read-only.

Commit

Permalink
Merge branch 'feature/track_by' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Burgmer committed Feb 3, 2016
2 parents 54ab150 + e7db96e commit 2e51a00
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 53 deletions.
132 changes: 81 additions & 51 deletions src/w11k-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ angular.module('w11k.select').constant('w11kSelectConfig', {

angular.module('w11k.select').factory('w11kSelectHelper', ['$parse', '$document', function ($parse, $document) {

// value as label for item in collection
var OPTIONS_EXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
// value as label for item in collection | filter track by tracking
var OPTIONS_EXP = /^([a-zA-Z][\w\.]*)(?:\s+as\s+([a-zA-Z][\w\.]*))?\s+for\s+(?:([a-zA-Z][\w]*))\s+in\s+([a-zA-Z][\w\.]*(?:\s+\|\s[a-zA-Z][\w\:]*)*)(?:\s+track\sby\s+([a-zA-Z][\w\.]*))?$/;

function extendDeep(dst) {
angular.forEach(arguments, function (obj) {
Expand Down Expand Up @@ -121,7 +121,7 @@ angular.module('w11k.select').factory('w11kSelectHelper', ['$parse', '$document'

var match = input.match(OPTIONS_EXP);
if (!match) {
var expected = '"value" [as "label"] for "item" in "collection"';
var expected = '"item.value" [as "item.label"] for "item" in "collection [ | filter ] [track by item.value.unique]"';
throw new Error('Expected options in form of \'' + expected + '\' but got "' + input + '".');
}

Expand All @@ -132,6 +132,10 @@ angular.module('w11k.select').factory('w11kSelectHelper', ['$parse', '$document'
collection: $parse(match[4])
};

if (match[5] !== undefined) {
result.tracking = $parse(match[5]);
}

return result;
}

Expand Down Expand Up @@ -199,7 +203,13 @@ angular.module('w11k.select').directive('w11kSelect', [
compile: function (tElement, tAttrs) {
var configExpParsed = $parse(tAttrs.w11kSelectConfig);
var optionsExpParsed = w11kSelectHelper.parseOptions(tAttrs.w11kSelectOptions);

var ngModelSetter = $parse(tAttrs.ngModel).assign;
var assignValueFn = optionsExpParsed.value.assign;

if(optionsExpParsed.tracking !== undefined && assignValueFn === undefined) {
throw new Error('value part of w11kSelectOptions expression must be assignable if \'track by\' is used');
}

return function (scope, iElement, iAttrs, controller) {
var domElement = iElement[0];
Expand All @@ -209,8 +219,8 @@ angular.module('w11k.select').directive('w11kSelect', [
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

var hasBeenOpened = false;
var options = [];
var optionsMap = {};
var internalOptions = [];
var internalOptionsMap = {};
var optionsFiltered = [];

scope.options = {
Expand Down Expand Up @@ -274,7 +284,7 @@ angular.module('w11k.select').directive('w11kSelect', [

function checkSelection() {
if (scope.config.multiple === false) {
var selectedOptions = options.filter(function (option) {
var selectedOptions = internalOptions.filter(function (option) {
return option.selected;
});

Expand Down Expand Up @@ -432,7 +442,7 @@ angular.module('w11k.select').directive('w11kSelect', [
domHeaderText.textContent = scope.$parent.$eval(scope.config.header.text);
}
else {
var optionsSelected = options.filter(function (option) {
var optionsSelected = internalOptions.filter(function (option) {
return option.selected;
});

Expand All @@ -455,7 +465,7 @@ angular.module('w11k.select').directive('w11kSelect', [
function filterOptions() {
if (hasBeenOpened) {
// false as third parameter: use contains to compare
optionsFiltered = filter(options, scope.filter.values, false);
optionsFiltered = filter(internalOptions, scope.filter.values, false);
scope.options.visible = optionsFiltered.slice(0, initialLimitTo);
}
}
Expand Down Expand Up @@ -518,45 +528,47 @@ angular.module('w11k.select').directive('w11kSelect', [
$event.stopPropagation();
}

setSelected(options, false);
setSelected(internalOptions, false);
setViewValue();
};

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* options
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

function collection2options(collection, viewValue) {
var viewValueHashes = {};
function externalOptions2internalOptions(externalOptions, viewValue) {
var viewValueIDs = {};

var i = viewValue.length;
while (i--) {
var hash = w11kSelectHelper.hashCode(viewValue[i]);
viewValueHashes[hash] = true;
var trackingId = value2trackingId(viewValue[i]);
viewValueIDs[trackingId] = true;
}

var options = collection.map(function (element) {
var optionValue = modelElement2value(element);
var optionValueHash = w11kSelectHelper.hashCode(optionValue);
var optionLabel = modelElement2label(element);
var internalOptions = externalOptions.map(function (externalOption) {
var value = externalOption2value(externalOption);
var trackingId = value2trackingId(value);
var label = externalOption2label(externalOption);

var selected;
if (viewValueHashes[optionValueHash]) {
if (viewValueIDs[trackingId]) {
selected = true;
}
else {
selected = false;
}

return {
hash: optionValueHash,
label: optionLabel,
model: element,
var internalOption = {
trackingId: trackingId,
label: label,
model: externalOption,
selected: selected
};

return internalOption;
});

return options;
return internalOptions;
}

var optionsAlreadyRead;
Expand All @@ -566,20 +578,20 @@ angular.module('w11k.select').directive('w11kSelect', [
optionsAlreadyRead = deferred.promise;

return function updateOptions() {
var collection = optionsExpParsed.collection(scope.$parent);
var externalOptions = optionsExpParsed.collection(scope.$parent);
var viewValue = controller.$viewValue;

if (angular.isArray(collection)) {
options = collection2options(collection, viewValue);
if (angular.isArray(externalOptions)) {
internalOptions = externalOptions2internalOptions(externalOptions, viewValue);

optionsMap = {};
var i = options.length;
internalOptionsMap = {};
var i = internalOptions.length;
while (i--) {
var option = options[i];
if (optionsMap[option.hash]) {
throw new Error('Duplicate hash value for options ' + option.label + ' and ' + optionsMap[option.hash].label);
var option = internalOptions[i];
if (internalOptionsMap[option.trackingId]) {
throw new Error('Duplicate hash value for options ' + option.label + ' and ' + internalOptionsMap[option.trackingId].label);
}
optionsMap[option.hash] = option;
internalOptionsMap[option.trackingId] = option;
}

filterOptions();
Expand All @@ -594,19 +606,19 @@ angular.module('w11k.select').directive('w11kSelect', [

// watch for changes of options collection made outside
scope.$watchCollection(
function () {
function externalOptionsWatch() {
return optionsExpParsed.collection(scope.$parent);
},
function (newVal) {
function externalOptionsWatchAction(newVal) {
if (angular.isDefined(newVal)) {
updateOptions();
}
}
);

scope.select = function (option) {
scope.select = function select(option) {
if (option.selected === false && scope.config.multiple === false) {
setSelected(options, false);
setSelected(internalOptions, false);
option.selected = true;

scope.dropdown.close();
Expand All @@ -624,7 +636,7 @@ angular.module('w11k.select').directive('w11kSelect', [
};

// called on click to a checkbox of an option
scope.onOptionStateClick = function ($event, option) {
scope.onOptionStateClick = function onOptionStateClick($event, option) {
// we have to stop propagation, otherwise selected state will be toggled twice
// because of click handler of list element
$event.stopPropagation();
Expand All @@ -641,14 +653,14 @@ angular.module('w11k.select').directive('w11kSelect', [
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

function setViewValue() {
var selectedValues = options2model(options);
var selectedValues = internalOptions2externalModel(internalOptions);

controller.$setViewValue(selectedValues);
updateHeader();
}

function updateNgModel() {
var value = options2model(options);
var value = internalOptions2externalModel(internalOptions);

angular.forEach(controller.$parsers, function (parser) {
value = parser(value);
Expand All @@ -666,12 +678,12 @@ angular.module('w11k.select').directive('w11kSelect', [

var viewValue = controller.$viewValue;

setSelected(options, false);
setSelected(internalOptions, false);

var i = viewValue.length;
while (i--) {
var hash = w11kSelectHelper.hashCode(viewValue[i]);
var option = optionsMap[hash];
var trackingId = value2trackingId(viewValue[i]);
var option = internalOptionsMap[trackingId];

if (option) {
option.selected = true;
Expand All @@ -682,6 +694,24 @@ angular.module('w11k.select').directive('w11kSelect', [
});
}

function value2trackingId(value) {
if (optionsExpParsed.tracking !== undefined) {
var context = {};
assignValueFn(context, value);

var trackingValue = optionsExpParsed.tracking(context);

if (trackingValue === undefined) {
throw new Error('Couldn\'t get \'track by\' value. Please make sure to only use something in \'track by’ part of w11kSelectOptions expression, accessible from result of value part. (\'option.data\' and \'option.data.unique\' but not \'option.unique\')');
}

return trackingValue.toString();
}
else {
return w11kSelectHelper.hashCode(value);
}

}

function external2internal(modelValue) {
var viewValue;
Expand Down Expand Up @@ -756,30 +786,30 @@ angular.module('w11k.select').directive('w11kSelect', [
}
}

function options2model(options) {
function internalOptions2externalModel(options) {
var selectedOptions = options.filter(function (option) {
return option.selected;
return option.selected;
});

var selectedValues = selectedOptions.map(option2value);
var selectedValues = selectedOptions.map(internalOption2value);

return selectedValues;
}

function option2value(option) {
return modelElement2value(option.model);
function internalOption2value(option) {
return externalOption2value(option.model);
}

function modelElement2value(modelElement) {
function externalOption2value(option) {
var context = {};
context[optionsExpParsed.item] = modelElement;
context[optionsExpParsed.item] = option;

return optionsExpParsed.value(context);
}

function modelElement2label(modelElement) {
function externalOption2label(option) {
var context = {};
context[optionsExpParsed.item] = modelElement;
context[optionsExpParsed.item] = option;

return optionsExpParsed.label(context);
}
Expand Down
2 changes: 1 addition & 1 deletion src/w11k-select.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</div>
<div class="content" w11k-select-infinite-scroll="showMoreOptions()" w11k-select-infinite-scroll-distance="0.5" ng-class="{'hidden-checkboxes': (config.hideCheckboxes && !config.multiple)}">
<ul class="items list-unstyled">
<li bindonce="option" ng-repeat="option in options.visible track by option.hash" ng-click="select(option)" ng-class="{'selected': option.selected }">
<li bindonce="option" ng-repeat="option in options.visible track by option.trackingId" ng-click="select(option)" ng-class="{'selected': option.selected }">
<div class="state">
<input type="checkbox" ng-checked="option.selected" ng-click="onOptionStateClick($event, option)">
</div>
Expand Down
2 changes: 1 addition & 1 deletion test/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<div class="col-sm-6">
<div w11k-select
w11k-select-config="[staticConfig, dynamicConfig]"
w11k-select-options="option.value as option.label for option in options.data"
w11k-select-options="option.value as option.label for option in options.data | limitTo:5 track by option.value"
ng-model="selected.data"
name="demoField"
>
Expand Down

0 comments on commit 2e51a00

Please sign in to comment.