From 9261752068536b12b49b20992822c9a2f78edde4 Mon Sep 17 00:00:00 2001
From: Kushan Joshi <0o3ko0@gmail.com>
Date: Sun, 3 Apr 2016 12:39:43 +0530
Subject: [PATCH] add multiselect preset
---
Makefile | 1 +
css/app.css | 52 +++++
data/presets.yaml | 29 +--
data/presets/fields.json | 48 +----
data/presets/fields/internet_access.json | 2 +-
data/presets/fields/recycling/cans.json | 5 -
data/presets/fields/recycling/clothes.json | 5 -
data/presets/fields/recycling/glass.json | 5 -
.../fields/recycling/glass_bottles.json | 5 -
data/presets/fields/recycling/paper.json | 5 -
data/presets/fields/recycling/plastic.json | 5 -
.../fields/recycling/recycling_choices.json | 5 +
data/presets/fields/recycling/type.json | 11 -
data/presets/fields/sport_ice.json | 2 +-
data/presets/presets.json | 8 +-
data/presets/presets/amenity/recycling.json | 8 +-
data/presets/schema/field.json | 1 +
dist/locales/en.json | 26 +--
index.html | 1 +
js/id/ui/preset/multiselect.js | 194 ++++++++++++++++++
js/lib/d3.combobox.js | 4 +-
21 files changed, 271 insertions(+), 151 deletions(-)
delete mode 100644 data/presets/fields/recycling/cans.json
delete mode 100644 data/presets/fields/recycling/clothes.json
delete mode 100644 data/presets/fields/recycling/glass.json
delete mode 100644 data/presets/fields/recycling/glass_bottles.json
delete mode 100644 data/presets/fields/recycling/paper.json
delete mode 100644 data/presets/fields/recycling/plastic.json
create mode 100644 data/presets/fields/recycling/recycling_choices.json
delete mode 100644 data/presets/fields/recycling/type.json
create mode 100644 js/id/ui/preset/multiselect.js
diff --git a/Makefile b/Makefile
index edd31d1e9..adcf491a7 100644
--- a/Makefile
+++ b/Makefile
@@ -232,6 +232,7 @@ dist/iD.js: \
js/id/ui/preset/address.js \
js/id/ui/preset/check.js \
js/id/ui/preset/combo.js \
+ js/id/ui/preset/multiselect.js \
js/id/ui/preset/cycleway.js \
js/id/ui/preset/input.js \
js/id/ui/preset/localized.js \
diff --git a/css/app.css b/css/app.css
index c29f6429b..345e5af73 100644
--- a/css/app.css
+++ b/css/app.css
@@ -1193,6 +1193,58 @@ button.save.has-count .count::before {
border-bottom-right-radius: 4px;
}
+/* preset form multiselect */
+
+.form-field-multiselect {
+ border: 1px solid #cfcfcf;
+ border-top: 0px;
+ padding: 5px 0 5px 10px;
+ background: #fff;
+ display: block;
+ border-radius: 0 0 4px 4px;
+ overflow: hidden;
+}
+
+.form-field-multiselect:focus {
+ border-bottom: 0px;
+}
+
+.form-field-multiselect.active {
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+}
+
+.form-field-multiselect li {
+ background-color: #eff2f7;
+ border: 1px solid #ccd5e3;
+ border-radius: 4px;
+ line-height: 25px;
+ display: inline-block;
+ padding: 2px 5px;
+ margin: 0 10px 5px 0;
+ height: 30px;
+}
+
+.form-field-multiselect a {
+ font-family: Arial, Helvetica, sans-serif !important;
+ font-size: 16px !important;
+ line-height: 25px;
+ float: right;
+ margin: 1px 0 0 5px;
+ padding: 0;
+ cursor: pointer;
+ color: #a6b4ce;
+}
+
+.form-field-multiselect input {
+ border: 0px;
+ width: 110px;
+}
+
+.form-field-multiselect input:focus {
+ border-radius: 4px !important;
+}
+
/* preset form cycleway */
.form-field-cycleway .preset-input-wrap li {
diff --git a/data/presets.yaml b/data/presets.yaml
index 8884e3cbf..c79e1c932 100644
--- a/data/presets.yaml
+++ b/data/presets.yaml
@@ -773,32 +773,9 @@ en:
railway:
# 'railway=*'
label: Type
- recycling/cans:
- # 'recycling:cans=*'
- label: Accepts Cans
- recycling/clothes:
- # 'recycling:clothes=*'
- label: Accepts Clothes
- recycling/glass:
- # 'recycling:glass=*'
- label: Accepts Glass
- recycling/glass_bottles:
- # 'recycling:glass_bottles=*'
- label: Accepts Glass Bottles
- recycling/paper:
- # 'recycling:paper=*'
- label: Accepts Paper
- recycling/plastic:
- # 'recycling:plastic=*'
- label: Accepts Plastic
- recycling/type:
- # 'recycling_type=*'
- label: Recycling Type
- options:
- # recycling_type=centre
- centre: Recycling Center
- # recycling_type=container
- container: Container
+ recycling/recycling_choices:
+ # 'recycling=*'
+ label: Accepts
ref:
# 'ref=*'
label: Reference
diff --git a/data/presets/fields.json b/data/presets/fields.json
index d2aec1869..1c7bb7395 100644
--- a/data/presets/fields.json
+++ b/data/presets/fields.json
@@ -653,7 +653,7 @@
},
"internet_access": {
"key": "internet_access",
- "type": "combo",
+ "type": "multiselect",
"label": "Internet Access",
"strings": {
"options": {
@@ -1020,46 +1020,10 @@
"type": "typeCombo",
"label": "Type"
},
- "recycling/cans": {
- "key": "recycling:cans",
- "type": "check",
- "label": "Accepts Cans"
- },
- "recycling/clothes": {
- "key": "recycling:clothes",
- "type": "check",
- "label": "Accepts Clothes"
- },
- "recycling/glass": {
- "key": "recycling:glass",
- "type": "check",
- "label": "Accepts Glass"
- },
- "recycling/glass_bottles": {
- "key": "recycling:glass_bottles",
- "type": "check",
- "label": "Accepts Glass Bottles"
- },
- "recycling/paper": {
- "key": "recycling:paper",
- "type": "check",
- "label": "Accepts Paper"
- },
- "recycling/plastic": {
- "key": "recycling:plastic",
- "type": "check",
- "label": "Accepts Plastic"
- },
- "recycling/type": {
- "key": "recycling_type",
- "type": "combo",
- "label": "Recycling Type",
- "strings": {
- "options": {
- "container": "Container",
- "centre": "Recycling Center"
- }
- }
+ "recycling/recycling_choices": {
+ "key": "recycling",
+ "type": "multiselect",
+ "label": "Accepts"
},
"ref": {
"key": "ref",
@@ -1270,7 +1234,7 @@
},
"sport_ice": {
"key": "sport",
- "type": "combo",
+ "type": "multiselect",
"label": "Sport",
"options": [
"skating",
diff --git a/data/presets/fields/internet_access.json b/data/presets/fields/internet_access.json
index a628dfd5f..ec5ca1f7c 100644
--- a/data/presets/fields/internet_access.json
+++ b/data/presets/fields/internet_access.json
@@ -1,6 +1,6 @@
{
"key": "internet_access",
- "type": "combo",
+ "type": "multiselect",
"label": "Internet Access",
"strings": {
"options": {
diff --git a/data/presets/fields/recycling/cans.json b/data/presets/fields/recycling/cans.json
deleted file mode 100644
index 8829418c6..000000000
--- a/data/presets/fields/recycling/cans.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "key": "recycling:cans",
- "type": "check",
- "label": "Accepts Cans"
-}
diff --git a/data/presets/fields/recycling/clothes.json b/data/presets/fields/recycling/clothes.json
deleted file mode 100644
index f90dfaac6..000000000
--- a/data/presets/fields/recycling/clothes.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "key": "recycling:clothes",
- "type": "check",
- "label": "Accepts Clothes"
-}
diff --git a/data/presets/fields/recycling/glass.json b/data/presets/fields/recycling/glass.json
deleted file mode 100644
index df6b23073..000000000
--- a/data/presets/fields/recycling/glass.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "key": "recycling:glass",
- "type": "check",
- "label": "Accepts Glass"
-}
diff --git a/data/presets/fields/recycling/glass_bottles.json b/data/presets/fields/recycling/glass_bottles.json
deleted file mode 100644
index ab4621f7b..000000000
--- a/data/presets/fields/recycling/glass_bottles.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "key": "recycling:glass_bottles",
- "type": "check",
- "label": "Accepts Glass Bottles"
-}
diff --git a/data/presets/fields/recycling/paper.json b/data/presets/fields/recycling/paper.json
deleted file mode 100644
index 49e09c46e..000000000
--- a/data/presets/fields/recycling/paper.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "key": "recycling:paper",
- "type": "check",
- "label": "Accepts Paper"
-}
diff --git a/data/presets/fields/recycling/plastic.json b/data/presets/fields/recycling/plastic.json
deleted file mode 100644
index 2bb43abd4..000000000
--- a/data/presets/fields/recycling/plastic.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "key": "recycling:plastic",
- "type": "check",
- "label": "Accepts Plastic"
-}
diff --git a/data/presets/fields/recycling/recycling_choices.json b/data/presets/fields/recycling/recycling_choices.json
new file mode 100644
index 000000000..9500270e6
--- /dev/null
+++ b/data/presets/fields/recycling/recycling_choices.json
@@ -0,0 +1,5 @@
+{
+ "key": "recycling",
+ "type": "multiselect",
+ "label": "Accepts"
+}
diff --git a/data/presets/fields/recycling/type.json b/data/presets/fields/recycling/type.json
deleted file mode 100644
index e6d44574b..000000000
--- a/data/presets/fields/recycling/type.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "key": "recycling_type",
- "type": "combo",
- "label": "Recycling Type",
- "strings": {
- "options": {
- "container": "Container",
- "centre": "Recycling Center"
- }
- }
-}
diff --git a/data/presets/fields/sport_ice.json b/data/presets/fields/sport_ice.json
index 7e5133bf9..1d42d5fb8 100644
--- a/data/presets/fields/sport_ice.json
+++ b/data/presets/fields/sport_ice.json
@@ -1,6 +1,6 @@
{
"key": "sport",
- "type": "combo",
+ "type": "multiselect",
"label": "Sport",
"options": [
"skating",
diff --git a/data/presets/presets.json b/data/presets/presets.json
index 88e740558..3b1318b06 100644
--- a/data/presets/presets.json
+++ b/data/presets/presets.json
@@ -1581,13 +1581,7 @@
"fields": [
"operator",
"address",
- "recycling/type",
- "recycling/cans",
- "recycling/glass_bottles",
- "recycling/paper",
- "recycling/glass",
- "recycling/plastic",
- "recycling/clothes"
+ "recycling/recycling_choices"
],
"geometry": [
"point",
diff --git a/data/presets/presets/amenity/recycling.json b/data/presets/presets/amenity/recycling.json
index 5651c5774..23865fe5e 100644
--- a/data/presets/presets/amenity/recycling.json
+++ b/data/presets/presets/amenity/recycling.json
@@ -3,13 +3,7 @@
"fields": [
"operator",
"address",
- "recycling/type",
- "recycling/cans",
- "recycling/glass_bottles",
- "recycling/paper",
- "recycling/glass",
- "recycling/plastic",
- "recycling/clothes"
+ "recycling/recycling_choices"
],
"geometry": [
"point",
diff --git a/data/presets/schema/field.json b/data/presets/schema/field.json
index e43366ce5..89ac31459 100644
--- a/data/presets/schema/field.json
+++ b/data/presets/schema/field.json
@@ -56,6 +56,7 @@
"defaultcheck",
"text",
"maxspeed",
+ "multiselect",
"number",
"tel",
"email",
diff --git a/dist/locales/en.json b/dist/locales/en.json
index 2a5423794..8f41248e7 100644
--- a/dist/locales/en.json
+++ b/dist/locales/en.json
@@ -1277,30 +1277,8 @@
"railway": {
"label": "Type"
},
- "recycling/cans": {
- "label": "Accepts Cans"
- },
- "recycling/clothes": {
- "label": "Accepts Clothes"
- },
- "recycling/glass": {
- "label": "Accepts Glass"
- },
- "recycling/glass_bottles": {
- "label": "Accepts Glass Bottles"
- },
- "recycling/paper": {
- "label": "Accepts Paper"
- },
- "recycling/plastic": {
- "label": "Accepts Plastic"
- },
- "recycling/type": {
- "label": "Recycling Type",
- "options": {
- "container": "Container",
- "centre": "Recycling Center"
- }
+ "recycling/recycling_choices": {
+ "label": "Accepts"
},
"ref": {
"label": "Reference"
diff --git a/index.html b/index.html
index 80d184a2f..fc67bc970 100644
--- a/index.html
+++ b/index.html
@@ -129,6 +129,7 @@
+
diff --git a/js/id/ui/preset/multiselect.js b/js/id/ui/preset/multiselect.js
new file mode 100644
index 000000000..ad54d2230
--- /dev/null
+++ b/js/id/ui/preset/multiselect.js
@@ -0,0 +1,194 @@
+iD.ui.preset.multiselect = function(field, context) {
+ var dispatch = d3.dispatch('init', 'change'),
+ optstrings = field.strings && field.strings.options,
+ optarray = field.options,
+ strings = {},
+ multiselectContainer,
+ combobox,
+ comboboxData,
+ input,
+ isInitialized;
+
+ field.key += ':';
+
+ function getOptStringKey(val) {
+ if (optstrings) {
+ var match = _.find(strings, function(o) {
+ return o.value === val;
+ });
+ return match && match.key;
+ }
+ }
+
+ function getOptStringVal(key) {
+ if (optstrings) {
+ var match = _.find(strings, function(o) {
+ return o.key === key;
+ });
+ return match && match.value;
+ }
+ }
+
+ function objectDifference(a, b) {
+ var bObj = {};
+ b.forEach(function(obj){
+ bObj[obj.key] = obj;
+ });
+ // Return all elements in a, unless in b
+ return a.filter(function(obj){
+ return !(obj.key in bObj);
+ });
+ }
+
+ function multiselect(selection) {
+ isInitialized = false;
+ combobox = d3.combobox();
+
+ multiselectContainer = selection.selectAll('ul').data([0]);
+
+ multiselectContainer.enter()
+ .append('ul')
+ .on('click', function() {
+ window.setTimeout(function(){input.node().focus();}, 100);
+ })
+ .attr('class', 'form-field-multiselect');
+
+ input = multiselectContainer.selectAll('input')
+ .data([0]);
+
+ var enter = input.enter()
+ .append('input')
+ .attr('type', 'text')
+ .attr('id', 'preset-input-' + field.id);
+
+ if (optstrings) { enter.attr('readonly', 'readonly'); }
+
+ input
+ .call(function() {combobox(input, selection);})
+ .on('change', change)
+ .on('blur', change)
+ .on('focus', function() {multiselectContainer.classed('active', true);})
+ .each(function() {
+ if (optstrings) {
+ strings = Object.keys(optstrings).map(function(k) {
+ return {
+ key: k,
+ value: field.t('options.' + k, { 'default': optstrings[k] })
+ };
+ });
+ dispatch.init();
+ isInitialized = true;
+ } else if (optarray) {
+ strings = optarray.map(function(k) {return {key: k, value: k};});
+ dispatch.init();
+ isInitialized = true;
+ } else if (context.taginfo()) {
+ context.taginfo().keys({query: field.key}, function(err, data) {
+ if (!err) {
+ strings = data.map(function(k) {
+ var d = k.value.replace(field.key, '');
+ return {
+ key: d,
+ value: d
+ };
+ });
+ dispatch.init();
+ isInitialized = true;
+ }
+ });
+ }
+ });
+ }
+
+ function updateStrings(tagsData) {
+ comboboxData = objectDifference(strings, tagsData);
+ combobox.data(comboboxData.map(comboValues));
+ input.attr('placeholder', field.placeholder() ||
+ ( 'Type here'));
+ }
+
+
+ function update(data) {
+ var chips = multiselectContainer.selectAll('.chips').data(data);
+
+ var chip = chips.enter()
+ .insert('li', 'input')
+ .attr('class', 'chips');
+
+ chip.append('span');
+ chip.append('a');
+
+ chips.select('span').text(function(d) {return d.value;});
+
+ chips.select('a')
+ .on('click', removeKey)
+ .attr('class', 'remove')
+ .text('×');
+
+ chips.exit().remove();
+ }
+
+ function comboValues(d) {
+ return {
+ value: d.value,
+ title: d.value
+ };
+ }
+
+ function change() {
+ multiselectContainer.classed('active', false);
+ var key = getOptStringKey(input.value()) || input.value();
+ if (key && key !== '') {
+ var t = {};
+ t[field.key + key] = 'yes';
+ input.value('');
+ dispatch.change(t);
+ }
+ }
+
+ function removeKey(d) {
+ d3.event.stopPropagation();
+ var t = {};
+ t[field.key + d.key] = undefined;
+ dispatch.change(t);
+ }
+
+ multiselect.tags = function(tags) {
+ var tagsData = [];
+ Object.keys(tags).forEach(function(d) {
+ if (d.indexOf(field.key) > -1 && tags[d] === 'yes') {
+ var datum = d.replace(field.key, '');
+
+ if (!optstrings) {
+ return tagsData.push({
+ key: datum,
+ value: datum
+ });
+ }
+ // discards any pair not found in optstrings
+ if (optstrings && getOptStringVal(datum)) {
+ return tagsData.push({
+ key: datum,
+ value: getOptStringVal(datum)
+ });
+ }
+ }
+ });
+
+ update(tagsData);
+
+ if (isInitialized) {
+ updateStrings(tagsData);
+ } else {
+ dispatch.on('init', function () {
+ updateStrings(tagsData);
+ });
+ }
+ };
+
+ multiselect.focus = function() {
+ input.node().focus();
+ };
+
+ return d3.rebind(multiselect, dispatch, 'on');
+};
diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js
index 083b1a970..bb605f582 100644
--- a/js/lib/d3.combobox.js
+++ b/js/lib/d3.combobox.js
@@ -13,7 +13,7 @@ d3.combobox = function() {
}));
};
- var combobox = function(input) {
+ var combobox = function(input, customBoundingRect) {
var idx = -1,
container = d3.select(document.body)
.selectAll('div.combobox')
@@ -222,7 +222,7 @@ d3.combobox = function() {
options.exit()
.remove();
- var rect = input.node().getBoundingClientRect();
+ var rect = customBoundingRect ? customBoundingRect.node().getBoundingClientRect() : input.node().getBoundingClientRect();
container.style({
'left': rect.left + 'px',