Skip to content

Instantly share code, notes, and snippets.

@mariechatfield
Last active May 3, 2017 20:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mariechatfield/0b7c521dd520786b18f57d0633315797 to your computer and use it in GitHub Desktop.
Save mariechatfield/0b7c521dd520786b18f57d0633315797 to your computer and use it in GitHub Desktop.
Location Filters
import Ember from 'ember';
// This component manages interaction with the location service, and wraps
// around the location-filter/location-dropdown component, which handles
// all the logic/UI of the actual picker
export default Ember.Component.extend({
location: Ember.inject.service(),
// Get all state from the service directly. Mark as readOnly to prevent
// accidental writes to service state.
currentUser: Ember.computed.readOnly('location.currentUser'),
isLoading: Ember.computed.readOnly('location.isLoading'),
multiple: Ember.computed.readOnly('location.multiple'),
units: Ember.computed.readOnly('location.units'),
sortedUnits: Ember.computed.readOnly('location.sortedUnits'),
selectedUnits: Ember.computed.readOnly('location.selectedUnits'),
didInsertElement() {
// This should no-op, provided that the route or controller has already
// loaded the units, but this will prevent the component from ever being
// rendered without location data
this.get('location').loadUnits();
},
actions: {
change(units) {
// Write change to location service (global state)
this.get('location').set('selectedUnits', units);
// Bubble up to parent
this.sendAction('change', units);
}
}
});
import Ember from 'ember';
import SqSubunitsFilter from '../sq-subunits-filter';
// This doesn't actually have to extend SqSubunitsFilter, but much of the
// business logic will be the same. We may want to rename some internal
// properties (e.g. get rid of "subunits" terminology) and change the API
// a bit to clean it up (e.g. only accept arrays, instead of either arrays
// or single objects)
export default SqSubunitsFilter.extend({
units: null,
sortedUnits: null,
isMultiunit: false,
subunits: Ember.computed.readOnly('units'),
subunitsSortedByNickname: Ember.computed.readOnly('sortedUnits'),
currentUser: Ember.computed('isMultiunit', function() {
return {
isMultiunit: this.get('isMultiunit')
};
})
});
import Ember from 'ember';
/**
`sq-dropdown-trigger` is intended to be used in `sq-dropdown`, it should not be used elsewhere.
*/
export default Ember.Component.extend({
classNames: ['dropdown__trigger'],
tagName: 'button',
classNameBindings: ['isActive:dropdown__trigger--is-active', 'isDisabled:dropdown__trigger--is-disabled'],
attributeBindings: ['isDisabled:disabled', 'tabindex', 'style'],
tabindex: 0
});
export default Ember.Component.extend({
classNames: ['dropdown'],
classNameBindings: ['isActive:dropdown-is-active'],
isDisabled: false,
willDestroyElement(...args) {
this._super(...args);
this.unbindApplicationClick();
},
bindApplicationClick() {
Ember.$(window).on(`click.${this.elementId} touchend.${this.elementId}`, (...args) => {
this.applicationClick(...args);
});
},
unbindApplicationClick() {
Ember.$(window).off(`.${this.elementId}`);
},
applicationClick(e) {
if (this.get('isDestroyed') || this.get('isDestroying')) {
return;
}
const inPopover = this.$().find(e.target).length;
if (!inPopover) {
this.send('close');
}
},
actions: {
toggleActive() {
if (this.get('isActive')) {
this.send('close');
} else {
this.send('open');
}
},
open() {
this.sendAction('onOpen');
this.set('isActive', true);
Ember.run.next(() => this.bindApplicationClick());
},
close() {
this.sendAction('onClose');
this.set('isActive', false);
this.unbindApplicationClick();
}
}
});
import SqOptionComponent from './sq-option';
import Ember from 'ember';
export default SqOptionComponent.extend({
_$input: null,
additionalCheckboxClassNames: null,
checkboxClassNames: Ember.computed('isActive', function() {
const classes = ['checkbox'];
if (this.get('isActive')) {
classes.push('checkbox-is-active');
}
if (this.get('additionalCheckboxClassNames')) {
classes.push(this.get('additionalCheckboxClassNames'));
}
return classes.join(' ');
}),
isActiveDidChange: Ember.observer('isActive', function() {
if (this._$input !== undefined && this._$input !== null) {
this._$input.prop('checked', this.get('isActive'));
}
}),
name: Ember.computed('parentView.elementId', function() {
return `option-set--${this.get('parentView.elementId')}`;
}),
assignInput: Ember.on('didInsertElement', function() {
this._$input = this.$('input');
this.isActiveDidChange();
})
});
import Ember from 'ember';
import SqOptionComponent from './sq-option';
export default SqOptionComponent.extend({
_$input: null,
isFocused: false,
radioClassNames: Ember.computed('isActive', 'isFocused', function() {
const radioClasses = ['radio'];
if (this.get('isActive')) {
radioClasses.push('radio-is-active');
}
if (this.get('isFocused')) {
radioClasses.push('radio--is-focused');
}
return radioClasses.join(' ');
}),
isActiveDidChange: Ember.observer('isActive', function() {
return (typeof this._$input !== 'undefined' && this._$input !== null) ? this._$input.prop('checked', this.get('isActive')) : undefined;
}),
name: Ember.computed('parentView.elementId', function() {
return `option-set--${this.get('parentView.elementId')}`;
}),
assignInput: Ember.on('didInsertElement', function() {
this._$input = this.$('input');
return this.isActiveDidChange();
}),
focusIn() {
this.set('isFocused', true);
},
focusOut() {
this.set('isFocused', false);
}
});
import Ember from 'ember';
/**
`SqOptionComponent` and `SqOptionSetComponent` offer a flexible solution for
`<input type='radio'>` and/or `<select> <option> </select>` behavior.
## Simple Example
Using an `{{#sq-option-set}}{{/sq-option-set}}`, you can easily access the
selected option by binding to its value.
```handlebars
{{#sq-option-set value=milkshake.flavor}}
{{#sq-option value="vanilla"}} Vanilla Victory {{/sq-option}}
{{#sq-option value="chocolate"}} Chocolate Caravan {{/sq-option}}
{{#sq-option value="strawberry"}} Surreal Strawberry {{/sq-option}}
{{/sq-option-set}}
```
Without an option set, each `{{sq-option}}` will be bound to its
`parentView` as it's created. The `parentView` object needs to implement
`addManagedOption` and `managedOptionClicked`.
Setting multiple to true on the option-set allows for multiple selections.
```handlebars
{{#sq-option-set multiple=true value=milkshake.flavor}}
{{#sq-option value="vanilla"}} Vanilla Victory {{/sq-option}}
{{#sq-option value="chocolate"}} Chocolate Caravan {{/sq-option}}
{{#sq-option value="strawberry"}} Surreal Strawberry {{/sq-option}}
{{/sq-option-set}}
```
## Kitchen Sink Example
You can extend a sq-option to be rendered as a div (by default), an input
(radio or checkbox) or anything you'd like.
```handlebars
{{!-- options can be a crazy mix of divs, radio or checkbox inputs --}}
{{#sq-option-set value=milkshake.flavor}}
{{#sq-option value="vanilla" tagName="input" type="radio"}} Vanilla Victory radio {{/sq-option}}
{{#sq-option value="chocolate" tagName="input" type="checkbox"}} Chocolate Caravan checkbox {{/sq-option}}
{{#sq-option value="strawberry"}} Surreal Strawberry div {{/sq-option}}
{{/sq-option-set}}
{{!-- An option set can be styled and used like a `<select>` tag --}}
{{#sq-option-set value=milkshake.flavor class="select"}}
{{#sq-option value="vanilla" class="select-option"}} Vanilla Victory {{/sq-option}}
{{#sq-option value="chocolate" class="select-option"}} Chocolate Caravan {{/sq-option}}
{{#sq-option value="strawberry" class="select-option"}} Surreal Strawberry {{/sq-option}}
{{/sq-option-set}}
```
## Options for use with Multiple.
Once multiple is set on the optionSet there are two other options you can enable.
- "default" can be set to indicate which option should be selected if the user unselects all of the options.
- "category" can be set on each of the SqOption's in the Set to indicate which category of object those options belong to
This allows a user to select as many objects from one category as possible but as soon as a new category is selected, the
selected items are cleared and the activeCategory is switched.
The activeCategory set initially on the SqOptionSet indicates which is the first active category if an item is selected by default.
defautlCategory represents the category to default back to in the event the selection is entirely cleared.
*/
export default Ember.Component.extend({
classNames: ['option-set'],
value: null,
managedOptions: null,
multiple: false,
activeCategory: null,
defaultCategory: null,
default: null,
invalidClass: 'input-is-invalid',
// This craziness enables mixin usages to override `invalidClass`
classNameBindings: ['_isInvalid'],
_isInvalid: Ember.computed('isInvalid', function() {
return this.get('isInvalid') ? this.get('invalidClass') : undefined;
}),
fieldName: null,
init(...args) {
this._super(...args);
if (this.get('fieldName')) {
this.connectView();
}
this.set('managedOptions', []);
if (!this.get('value') && this.get('multiple')) {
this.set('value', []);
}
if (!Ember.isNone(this.get('targetObject')) && !Ember.isNone(this.get('targetObject.content'))) {
this.get('targetObject').set('sqOptionSet', this);
}
},
cleanup: Ember.on('willDestroyElement', function() {
if (this.get('fieldName')) {
this.disconnectView();
}
}),
// Registers this view with its controller and binds to the appropriate
// property on the controller (e.g. bind value <-> controller.email).
connectView() {
},
// Cleans up the controller <-> view associations.
disconnectView() {
},
valueDidChange: Ember.observer('value.[]', 'value', function() {
this._syncActiveOptions();
}),
hoveredValueDidChange: Ember.observer('hoveredValue', function() {
this._syncHoveredOption();
}),
addManagedOption(option) {
this.get('managedOptions').pushObject(option);
if (this.get('multiple')) {
if (this.get('value').contains(option.get('value'))) {
option.set('isActive', true);
}
} else {
if (this.get('value') === option.get('value')) {
option.set('isActive', true);
}
}
if (this.get('hoveredValue') === option.get('value')) {
option.set('isHovered', true);
}
this._removeDestroyingOptions();
},
managedOptionClicked(clickedOption) {
if (this.get('multiple')) {
this._multipleManagedOptionClicked(clickedOption);
} else {
this._singleManagedOptionClicked(clickedOption);
}
},
managedOptionMouseEnter(option) {
this.set('hoveredValue', option.get('value'));
},
managedOptionMouseLeave() {
if (!this.get('retainHover')) {
this.set('hoveredValue', null);
}
},
_multipleManagedOptionClicked(clickedOption) {
const clickedValue = clickedOption.get('value');
const value = this.get('value');
if (value.contains(clickedValue)) {
value.removeObject(clickedValue);
// Reset the value to the default when the last option is removed
if (value.length === 0 && this.get('default')) {
value.pushObject(this.get('default'));
this.set('activeCategory', this.get('defaultCategory'));
}
} else {
// If the category of selection has changed, clear the values and switch the category
const newCategory = clickedOption.get('category');
if (this.get('activeCategory') && newCategory !== this.get('activeCategory')) {
value.clear();
this.set('activeCategory', newCategory);
}
value.pushObject(clickedValue);
}
this.sendAction('onChange', this.get('value'), clickedValue);
},
_removeDestroyingOptions() {
const options = this.get('managedOptions');
options.filterBy('isDestroying').forEach(option =>
options.removeObject(option)
);
},
_singleManagedOptionClicked(clickedOption) {
const clickedValue = clickedOption.get('value');
this.set('value', clickedValue);
this.sendAction('onChange', this.get('value'));
},
_syncActiveOptions() {
this._removeDestroyingOptions();
if (this.get('multiple')) {
this.get('managedOptions').forEach(option =>
option.set('isActive', this.get('value').contains(option.get('value'))));
} else {
this.get('managedOptions').forEach(option =>
option.set('isActive', this.get('value') === option.get('value')));
}
},
_syncHoveredOption() {
this._removeDestroyingOptions();
this.get('managedOptions').forEach(option =>
option.set('isHovered', this.get('hoveredValue') === option.get('value'))
);
},
actions: {
syncOptions() {
this._syncActiveOptions();
}
}
});
import Ember from 'ember';
import SqOptionSetComponent from './sq-option-set';
/**
`parentView` implements `addManagedOption` and `managedOptionClicked` they
are compatible
You may arrange for a tracking event to be fired on click by setting
trackingEventPath and/or trackingEventOptions in the usual way.
*/
export default Ember.Component.extend({
attributeBindings: ['disabled', 'type', 'checked', 'value'],
activeClass: 'option-is-active',
hoveredClass: 'option-is-hovered',
disabledClass: 'option-is-disabled',
classNames: ['option'],
classNameBindings: ['_isHovered', '_isDisabled'],
isDisabled: false,
isActive: false,
isInput: Ember.computed.equal('tagName', 'input'),
checked: Ember.computed.and('isActive', 'isInput'),
trackingData: null, // es2 tracking data
_isHovered: Ember.computed('isHovered', 'hoveredClass', function() {
return this.get('isHovered') ? this.get('hoveredClass') : null;
}),
_isDisabled: Ember.computed('isDisabled', 'disabledClass', function() {
return this.get('isDisabled') ? this.get('disabledClass') : null;
}),
// NOTE(mattwright): Something seems to have changed with Ember 1.13 where now,
// if one of these option components is wrapped in an `{{#each}}` (and they
// usually are), `_LegacyEachView` will be the `parentView`, not the
// `{{sq-option-set}}` component that we want it to be. This makes sure `group`
// points to the right thing in that case.
group: Ember.computed('parentView', function() {
let parentView = this;
while ((parentView !== null) && !SqOptionSetComponent.detectInstance(parentView)) {
parentView = parentView.get('parentView');
}
return parentView;
}),
init(...args) {
this._super(...args);
this.get('group').addManagedOption(this);
},
click() {
if (!this.get('isDisabled')) {
this.get('group').managedOptionClicked(this);
}
},
mouseEnter() {
this.get('group').managedOptionMouseEnter(this);
},
mouseLeave() {
this.get('group').managedOptionMouseLeave(this);
}
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['popover', '_isActive'],
activeClass: 'popover--is-active',
attributeBindings: ['style'],
_isActive: Ember.computed('isActive', function() {
if (this.get('isActive')) {
return this.get('activeClass');
}
return undefined;
})
});
import Ember from 'ember';
const { computed, get, makeArray, typeOf } = Ember;
const ALL_VALUE = 'allSubunits';
function copyAsArray(value) {
// unwrap array proxies
if (value && typeOf(value.toArray) === 'function') {
value = value.toArray();
}
return makeArray(value).slice();
}
function subunitsLabel(subunits, totalSubunitsCount, enabledAtAllUnits = false) {
return Ember.String.htmlSafe(
labelForSubunitList(
subunits.compact(),
totalSubunitsCount,
enabledAtAllUnits
)
);
}
function labelForSubunitList(subunits, totalSubunitsCount, enabledAtAllUnits) {
if (enabledAtAllUnits) {
return 'All Locations';
}
switch (subunits.length) {
case 0:
return 'No Locations';
case 1:
return subunits[0];
case 2: {
const [nickname1, nickname2] = caseInsensitiveSorted(subunits);
return `${nickname1} and ${nickname2}`;
}
case totalSubunitsCount:
return 'All Locations';
default:
return `${subunits.length} Locations`;
}
}
function caseInsensitiveSorted(strings) {
return strings.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1);
}
export default Ember.Component.extend({
classNames: ['sq-subunits-filter'],
isVisible: computed('currentUser.isMultiunit', 'hideIfOneUnit', 'subunitsSortedByNickname.length', function() {
// If the user is a single unit merchant, we should not show the unit filter since there are no units.
if (!this.get('currentUser.isMultiunit')) {
return false;
}
// Show the component regardless of the number of units the multiunit merchant has if flag is disabled.
if (!this.get('hideIfOneUnit')) {
return true;
}
// Show the component only if the multiunit merchant has more than one selection and the flag is enabled.
return this.get('subunitsSortedByNickname.length') > 1;
}),
// passed in
allowAll: false,
currentUser: null,
hideIfOneUnit: true,
multiple: false,
subunits: null,
triggerClass: 'button--pill caret-down',
value: null,
alignRight: false,
allValue: ALL_VALUE,
/*
NOTE: here is the tricky stuff!
The checkboxes should reflect the selected units exactly EXCEPT in one case:
after clicking the "Clear" button. Clicking Clear will uncheck all checkboxes,
but will not fire an action with new values. If the dropdown is dismissed
before making a new selection, we need to resync the internal state with the
external state.
The UI uses the `intervalValue` to determine which checkboxes are checked.
It's kept in sync in `didReceiveAttrs` and `actions.toggleDropdown`.
*/
internalValue: computed('value.[]', function() {
return copyAsArray(this.get('value'));
}),
didReceiveAttrs(...args) {
this._super(...args);
this.set('internalValue', copyAsArray(this.get('value')));
},
/* END TRICKY STUFF */
alignmentClasses: computed('alignRight', function() {
if (this.get('alignRight')) {
return 'popover--align-right pointer--align-right';
}
return 'pointer--align-left';
}),
popoverClass: computed('alignmentClasses', function() {
return [
'popover--fly-down l-filter--popover pointer--top popover-list',
this.get('alignmentClasses')
].join(' ');
}),
label: computed('multiple', 'value.[]', 'subunits.length', function() {
const value = copyAsArray(this.get('value'));
// `subunitsLabel` may return a nickname, a count, or "All Locations"
if (this.get('multiple')) {
return subunitsLabel(value.mapBy('nickname'), this.get('subunits.length'));
}
if (this.get('allowAll') && value.length === this.get('subunits.length')) {
return t('subunit.allSubunits');
}
return get(value, 'firstObject.nickname');
}),
singleValue: computed('value.[]', 'subunits', function() {
const value = copyAsArray(this.get('value'));
if (value.length === this.get('subunits.length')) {
return ALL_VALUE;
}
return value[0];
}),
subunitsSortedByNickname: computed('subunits.@each.nickname', function() {
return copyAsArray(this.get('subunits')).sortBy('nickname');
}),
actions: {
// multiple=false actions
singleValueChange(value) {
const units = value === ALL_VALUE ? this.get('subunits') : [value];
this.sendAction('change', units);
},
trackOpen() {
},
// multiple=true actions
// when opening the dropdown, ensure that the visible list of checked
// values is in sync with the actual values.
toggleDropdown() {
this.send('trackOpen');
this.set('internalValue', copyAsArray(this.get('value')));
},
multipleValueChange(value) {
this.sendAction('change', copyAsArray(value));
},
selectAll() {
const value = makeArray(this.get('value'));
if (value.length !== this.get('subunits.length')) {
this.sendAction('change', copyAsArray(this.get('subunits')));
// if we just hit "Clear", reset internal state to match
} else if (this.get('internalValue.length') === 0) {
this.set('internalValue', copyAsArray(this.get('subunits')));
}
},
clear() {
this.set('internalValue', []);
}
}
});
import Ember from 'ember';
export default Ember.Controller.extend({
location: Ember.inject.service(),
currentUser: Ember.computed.readOnly('model.currentUser'),
subunits: Ember.computed.readOnly('model.subunits'),
selectedSubunits: Ember.computed('subunits', function() {
return this.get('subunits.firstObject');
}),
actions: {
subunitFilterChange(subunits) {
this.set('selectedSubunits', subunits);
},
createNewLocation() {
this.get('location').createRandomLocation();
}
}
});
import Ember from 'ember';
// Controller doesn't have to care about selected data any more.
// Can use change action to run any API calls it may need to make,
// and if absolutely necessary it can look up selected data on service
export default Ember.Controller.extend({
location: Ember.inject.service(),
currentUser: Ember.computed.readOnly('location.currentUser'),
selectedUnits: Ember.computed('location.units', function() {
return this.get('location.units.firstObject');
}),
actions: {
subunitFilterChange(units) {
// Make some API call with units
},
subunitFilterChangeOverride(units) {
// Make some API call with units
this.set('selectedUnits', units);
}
}
});
import Ember from 'ember';
export default Ember.Route.extend({
location: Ember.inject.service(),
model() {
return Ember.RSVP.hash({
subunits: this.get('location').loadUnits(),
currentUser: this.get('location.currentUser')
});
}
});
import Ember from 'ember';
export default Ember.Route.extend({
location: Ember.inject.service(),
// Routes that use component should inject the location service
// and decide whether to use single or multiple locations. They
// can use the return value to look up queries based on the
// selected units if need be
model() {
return this.get('location').configure().then(units => {
// should be an actual API call
return Ember.RSVP.resolve({
units
});
});
}
});
import Ember from 'ember';
// Extra state for Twiddle examples
const STATES = [true, false];
const DEFAULT_LOCATION_NUMBER = 4;
let nextUnitToken = 0;
export default Ember.Service.extend({
isLoading: true,
multiple: true,
units: Ember.computed(function() {
return [];
}),
selectedUnits: Ember.computed(function() {
return [];
}),
sortedUnits: Ember.computed.sort('units', function (a, b) {
// Sort by active status, then by name
if (a.active === b.active) {
return a.nickname > b.nickname;
} else if (a.active) {
return -1;
} else {
return 1;
}
}),
loadUnits() {
// This should make a request to the server. Can be used to either
// resolve instantly if units have already been loaded, or to load
// units in the first place, whenever a route using the location-filter
// is being loaded.
return new Ember.RSVP.Promise(resolve => {
Ember.run.later(() => {
this.set('isLoading', false);
resolve(this.get('units'));
}, 500);
});
},
selectSingleToken(token, units) {
let selectedUnit = units[0];
if (token) {
selectedUnit = units.findBy('token', token) || selectedUnit;
}
return [selectedUnit];
},
selectMultipleTokens(tokens, units) {
let selectedUnits = units;
if (tokens) {
selectedUnits = units.filter(unit => tokens.includes(unit.get('token'))) || selectedUnits;
}
return selectedUnits;
},
configure({ tokens, single = false } = {}) {
this.setProperties({
isLoading: true,
selectedUnits: []
});
return this.loadUnits().then(units => {
const selectedUnits = single ?
this.selectSingleToken(tokens, units) :
this.selectMultipleTokens(tokens, units);
const multiple = single === false;
this.setProperties({
isLoading: false,
multiple,
selectedUnits,
units
});
return selectedUnits;
});
},
/* Extra helpers for Twiddle examples */
init() {
this._super(arguments);
for (let i = 0; i < DEFAULT_LOCATION_NUMBER; i++) {
this.createRandomLocation();
}
},
currentUser: Ember.computed(function() {
return {
isMultiunit: true
};
}),
createRandomLocation() {
const token = nextUnitToken++;
const active = STATES[Math.floor(Math.random() * STATES.length)];
const nickname = active ? `Location ${token} (Active)` : `Location ${token} (Inactive)`;
this.get('units').pushObject({token, nickname, active});
}
});
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
<button onClick={{action 'createNewLocation'}}>Add new location</button>
<h3>Current Implementation</h3>
{{sq-subunits-filter currentUser=currentUser
subunits=subunits
value=selectedSubunits
change="subunitFilterChange"
multiple=true}}
{{outlet}}
{{#if isLoading}}
Loading...
{{else}}
{{location-filter/location-dropdown change="change"
currentUser=currentUser
units=units
sortedUnits=sortedUnits
value=selectedUnits
multiple=multiple}}
{{/if}}
{{#if multiple}}
{{#sq-dropdown class="filter-merchant-units" onOpen=(action "toggleDropdown") as |dropdown|}}
{{dropdown.trigger label=label class="button" classNames=triggerClass}}
{{#dropdown.popover class=popoverClass}}
{{#sq-option-set value=internalValue
multiple=true
class="subunit-selector"
onChange=(action "multipleValueChange")}}
<div class="popover-list__scroll-box">
{{#each subunitsSortedByNickname as |unit|}}
{{#sq-option-checkbox value=unit class="popover-list__item popover-list__item--with-radio"}}
<div class="radio-row popover-list__radio-row">
{{unit.nickname}}
</div>
{{/sq-option-checkbox}}
{{/each}}
</div>
<div class="grid-row popover-list__item popover-list__footer {{if currentUser.isEnglishOrJapanese "popover-list__footer--divided"}} type-uppercase">
<div {{action (action "selectAll")}} class="popover-list__button {{if currentUser.isEnglishOrJapanese "grid-col grid-col-12-24" "popover-list__button--stacked"}} hyperlink popover-list__select-all-option">
Select All
</div>
<div {{action (action "clear")}} class="popover-list__button {{if currentUser.isEnglishOrJapanese "grid-col grid-col-12-24" "popover-list__button--stacked"}} hyperlink popover-list__clear-option">
Clear
</div>
</div>
{{/sq-option-set}}
{{/dropdown.popover}}
{{/sq-dropdown}}
{{else}}
{{#sq-dropdown class="filter-merchant-units" onOpen=(action "trackOpen") as |dropdown|}}
{{dropdown.trigger label=label class="button" classNames=triggerClass}}
{{#dropdown.popover class=popoverClass}}
{{#sq-option-set value=singleValue
click=dropdown.close
onChange=(action "singleValueChange")}}
<div class="popover-list__scroll-box">
{{#if allowAll}}
{{#sq-option-radio value=allValue class="popover-list__item popover-list__item--with-radio"}}
<div class="radio-row popover-list__radio-row">
{{t "subunit.allSubunits"}}
</div>
{{/sq-option-radio}}
{{/if}}
{{#each subunitsSortedByNickname as |unit|}}
{{#sq-option-radio value=unit class="popover-list__item popover-list__item--with-radio"}}
<div class="radio-row popover-list__radio-row">
{{unit.nickname}}
</div>
{{/sq-option-radio}}
{{/each}}
</div>
{{/sq-option-set}}
{{/dropdown.popover}}
{{/sq-dropdown}}
{{/if}}
{{#if hasBlock}}
{{yield}}
{{else}}
{{label}}
{{/if}}
{{yield (hash trigger=(component "sq-dropdown-trigger"
isDisabled=isDisabled
isActive=isActive
click=(unless isDisabled (action "toggleActive")))
popover=(component "sq-popover"
classNames="dropdown__popover"
isActive=isActive)
isDisabled=isDisabled
isActive=isActive
close=(action "close")
toggle=(unless isDisabled (action "toggleActive")))}}
<div class="{{checkboxClassNames}}">
<input type="checkbox" class="checkbox--input" name={{name}}>
<i class="checkbox--mark"></i>
</div>
{{yield}}
<div class="{{radioClassNames}}">
<input type="radio" class="radio--input" name={{name}} value={{value}}>
<i class="radio--mark"></i>
</div>
{{yield}}
{{#if multiple}}
{{#sq-dropdown class="filter-merchant-units" onOpen=(action "toggleDropdown") as |dropdown|}}
{{dropdown.trigger label=label class="button" classNames=triggerClass}}
{{#dropdown.popover class=popoverClass}}
{{#sq-option-set value=internalValue
multiple=true
class="subunit-selector"
onChange=(action "multipleValueChange")}}
<div class="popover-list__scroll-box">
{{#each subunitsSortedByNickname as |unit|}}
{{#sq-option-checkbox value=unit class="popover-list__item popover-list__item--with-radio"}}
<div class="radio-row popover-list__radio-row">
{{unit.nickname}}
</div>
{{/sq-option-checkbox}}
{{/each}}
</div>
<div class="grid-row popover-list__item popover-list__footer {{if currentUser.isEnglishOrJapanese "popover-list__footer--divided"}} type-uppercase">
<div {{action (action "selectAll")}} class="popover-list__button {{if currentUser.isEnglishOrJapanese "grid-col grid-col-12-24" "popover-list__button--stacked"}} hyperlink popover-list__select-all-option">
Select All
</div>
<div {{action (action "clear")}} class="popover-list__button {{if currentUser.isEnglishOrJapanese "grid-col grid-col-12-24" "popover-list__button--stacked"}} hyperlink popover-list__clear-option">
Clear
</div>
</div>
{{/sq-option-set}}
{{/dropdown.popover}}
{{/sq-dropdown}}
{{else}}
{{#sq-dropdown class="filter-merchant-units" onOpen=(action "trackOpen") as |dropdown|}}
{{dropdown.trigger label=label class="button" classNames=triggerClass}}
{{#dropdown.popover class=popoverClass}}
{{#sq-option-set value=singleValue
click=dropdown.close
onChange=(action "singleValueChange")}}
<div class="popover-list__scroll-box">
{{#if allowAll}}
{{#sq-option-radio value=allValue class="popover-list__item popover-list__item--with-radio"}}
<div class="radio-row popover-list__radio-row">
{{t "subunit.allSubunits"}}
</div>
{{/sq-option-radio}}
{{/if}}
{{#each subunitsSortedByNickname as |unit|}}
{{#sq-option-radio value=unit class="popover-list__item popover-list__item--with-radio"}}
<div class="radio-row popover-list__radio-row">
{{unit.nickname}}
</div>
{{/sq-option-radio}}
{{/each}}
</div>
{{/sq-option-set}}
{{/dropdown.popover}}
{{/sq-dropdown}}
{{/if}}
<h3>Proposed Implementation</h3>
<h4>Default <small>(Uses Service, is Magic)</small></h4>
{{location-filter change="subunitFilterChange"}}
<h4>Use dumb internal component directly if you really need two pickers on a single page <small>(but do you really?)</small></h4>
{{location-filter/location-dropdown change="subunitFilterChangeOverride"
isMultiunit=currentUser.isMultiunit
units=location.units
sortedUnits=location.sortedUnits
value=selectedUnits
multiple=false}}
{
"version": "0.12.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.12.0",
"ember-template-compiler": "2.12.0",
"ember-testing": "2.12.0"
},
"addons": {
"ember-data": "2.12.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment