UNPKG

4.33 kBJavaScriptView Raw
1import angular from 'angular';
2
3import deepEqual from 'deep-equal';
4
5class Options {
6 static OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+select\s+as\s+(.*?))?(?:\s+describe\sas\s+(.*?))?(?:\s+for\s+)?([$\w]+)\s+in\s+(.*?)(?:\s+track\sby\s+(.*?))?$/;
7
8 static MATCHES = {
9 ITEM: 1,
10 LABEL: 2,
11 SELECTED_LABEL: 3,
12 DESCRIPTION: 4,
13 OPTION: 5,
14 ITEMS: 6,
15 TRACK: 7
16 };
17
18 static defaultKeyField = 'key';
19 static defaultLabelField = 'label';
20 static defaultSelectedLabelField = 'selectedLabel';
21 static defaultDescriptionField = 'description';
22
23 constructor(scope, optionsString) {
24 this.scope = scope;
25 const $parse = this.constructor.$parse;
26 const MATCHES = this.constructor.MATCHES;
27
28 let match;
29 if (!(match = optionsString.match(this.constructor.OPTIONS_REGEXP))) {
30 throw new Error('Bad rgSelect expression format. Expected: [{item}] [[as] item.text] [select as item.selectLabel]' +
31 ` [describe as {item.description}] [for] {item} in {items|dataSource(query)} [track by item.id], Received: ${optionsString}`);
32 }
33
34 /**
35 * Now we can write only `item.value as item.label for item in items`
36 * we can not skip `item.label`
37 */
38 this.hasItemGetter = Boolean(match[MATCHES.ITEM] && match[MATCHES.LABEL]);
39
40 this.itemGetter = $parse(match[MATCHES.ITEM]);
41 this.labelGetter = (match[MATCHES.LABEL] && $parse(match[MATCHES.LABEL])) || this.itemGetter;
42 this.selectedLabelGetter =
43 match[MATCHES.SELECTED_LABEL] &&
44 $parse(match[MATCHES.SELECTED_LABEL]);
45 this.descriptionGetter = match[MATCHES.DESCRIPTION] && $parse(match[MATCHES.DESCRIPTION]);
46 this.optionVariableName = match[MATCHES.OPTION];
47 this.datasourceGetter = $parse(match[MATCHES.ITEMS]);
48 this.trackByGetter = match[MATCHES.TRACK] && $parse(match[MATCHES.TRACK]);
49 this.datasourceIsFunction = match[MATCHES.ITEMS].indexOf('(') > 0;
50 }
51
52 getProperty(option, getter) {
53 if (getter) {
54 const locals = {};
55 locals[this.optionVariableName] = option;
56 return getter.call(this, this.scope, locals);
57 }
58
59 return undefined;
60 }
61
62 /**
63 * @param {Object} option The item from options collection
64 * @return {any} The option value
65 */
66 getValue(option) {
67 if (!this.hasItemGetter) {
68 return option;
69 }
70
71 const value = this.getProperty(option, this.itemGetter);
72
73 return value === undefined ? option : value;
74 }
75
76 /**
77 * @param {any} value The option value
78 * @param {Array} options The list of options
79 * @return {Object|undefined} The option object
80 */
81 getOptionByValue(value, options) {
82 /**
83 * @param {any} it
84 * @return {string} The string representation of the value
85 */
86 function toString(it) {
87 return typeof it === 'object' ? JSON.stringify(it) : String(it);
88 }
89
90 if (!this.hasItemGetter) {
91 return value;
92 }
93
94 const matchedOptions = options.filter(option => {
95 const optionValue = this.getValue(option);
96
97 if (typeof value === 'object') {
98 return deepEqual(optionValue, value);
99 }
100
101 return optionValue === value;
102 });
103
104 if (matchedOptions.length > 1) {
105 throw new Error(`Error(rg-select): You can not have two options with same value(${toString(value)})`);
106 }
107
108 return matchedOptions[0];
109 }
110
111 getKey(option) {
112 return this.getProperty(option, this.trackByGetter) ||
113 option[this.constructor.defaultKeyField] ||
114 option;
115 }
116
117 getLabel(option) {
118 const optionStringValue = typeof option === 'string' ? option : null;
119 return this.getProperty(option, this.labelGetter) ||
120 option[this.constructor.defaultLabelField] ||
121 optionStringValue;
122 }
123
124 getSelectedLabel(option) {
125 return this.getProperty(option, this.selectedLabelGetter) ||
126 option[this.constructor.defaultSelectedLabelField];
127 }
128
129 getDescription(option) {
130 return this.getProperty(option, this.descriptionGetter) ||
131 option[this.constructor.defaultDescriptionField];
132 }
133
134 getOptions(query, skip) {
135 return this.datasourceGetter(this.scope, {query, skip});
136 }
137}
138
139
140const angularModule = angular.module('Ring.select.options', []);
141
142angularModule.factory('SelectOptions', function SelectOptionsFactory($parse) {
143 Options.$parse = $parse;
144 return Options;
145});
146
147export default angularModule.name;