Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

feat(chips): Make chips editable #7579

Closed
wants to merge 1 commit 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
5 changes: 5 additions & 0 deletions src/components/chips/chips-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ md-chips.md-THEME_NAME-theme {
color: '{{primary-contrast}}';
}
}

&._md-chip-editing {
background: transparent;
color: '{{background-800}}';
}
}
md-chip-remove {
.md-button {
Expand Down
6 changes: 6 additions & 0 deletions src/components/chips/chips.scss
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ $contact-chip-name-width: rem(12) !default;
outline: none;
}
}
&._md-chip-content-edit-is-enabled {
-webkit-user-select: none; /* webkit (safari, chrome) browsers */
-moz-user-select: none; /* mozilla browsers */
-khtml-user-select: none; /* webkit (konqueror) browsers */
-ms-user-select: none; /* IE10+ */
}
._md-chip-remove-container {
position: absolute;
@include rtl-prop(right, left, 0);
Expand Down
6 changes: 6 additions & 0 deletions src/components/chips/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ <h2 class="md-title">Use the default chip template.</h2>

<md-chips ng-model="ctrl.fruitNames" readonly="ctrl.readonly"></md-chips>


<br/>
<h2 class="md-title">Make chips editable.</h2>

<md-chips ng-model="ctrl.editableFruitNames" readonly="ctrl.readonly" md-enable-chip-edit="true"></md-chips>

<br/>
<h2 class="md-title">Use Placeholders and override hint texts.</h2>

Expand Down
2 changes: 2 additions & 0 deletions src/components/chips/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// Lists of fruit names and Vegetable objects
self.fruitNames = ['Apple', 'Banana', 'Orange'];
self.roFruitNames = angular.copy(self.fruitNames);
self.editableFruitNames = angular.copy(self.fruitNames);

self.tags = [];
self.vegObjs = [
{
Expand Down
192 changes: 192 additions & 0 deletions src/components/chips/js/chipController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
angular
.module('material.components.chips')
.controller('MdChipCtrl', MdChipCtrl);

/**
* Controller for the MdChip component. Responsible for handling keyboard
* events and editting the chip if needed.
*
* @param $scope
* @param $element
* @param $mdConstant
* @param $timeout
* @param $mdUtil
* @constructor
*/
function MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {
/**
* @type {$scope}
*/
this.$scope = $scope;

/**
* @type {$element}
*/
this.$element = $element;

/**
* @type {$mdConstant}
*/
this.$mdConstant = $mdConstant;

/**
* @type {$timeout}
*/
this.$timeout = $timeout;

/**
* @type {$mdUtil}
*/
this.$mdUtil = $mdUtil;

/**
* @type {boolean}
*/
this.isEditting = false;

/**
* @type {MdChipsCtrl}
*/
this.parentController = undefined;

/**
* @type {boolean}
*/
this.enableChipEdit = false;
}


/**
* @param {MdChipsCtrl} controller
*/
MdChipCtrl.prototype.init = function(controller) {
this.parentController = controller;
this.enableChipEdit = this.parentController.enableChipEdit;

if (this.enableChipEdit) {
this.$element.on('keydown', this.chipKeyDown.bind(this));
this.$element.on('mousedown', this.chipMouseDown.bind(this));
this.getChipContent().addClass('_md-chip-content-edit-is-enabled');
}
};


/**
* @return {Object}
*/
MdChipCtrl.prototype.getChipContent = function() {
var chipContents = this.$element[0].getElementsByClassName('_md-chip-content');
return angular.element(chipContents[0]);
};


/**
* @return {Object}
*/
MdChipCtrl.prototype.getContentElement = function() {
return angular.element(this.getChipContent().children()[0]);
};


/**
* @return {number}
*/
MdChipCtrl.prototype.getChipIndex = function() {
return parseInt(this.$element.attr('index'));
};


/**
* Presents an input element to edit the contents of the chip.
*/
MdChipCtrl.prototype.goOutOfEditMode = function() {
if (!this.isEditting) return;

this.isEditting = false;
this.$element.removeClass('_md-chip-editing');
this.getChipContent()[0].contentEditable = 'false';
var chipIndex = this.getChipIndex();

var content = this.getContentElement().text();
if (content) {
this.parentController.updateChipContents(
chipIndex,
this.getContentElement().text()
);

this.$mdUtil.nextTick(function() {
if (this.parentController.selectedChip === chipIndex) {
this.parentController.focusChip(chipIndex);
}
}.bind(this));
} else {
this.parentController.removeChipAndFocusInput(chipIndex);
}
};


/**
* Given an HTML element. Selects contents of it.
* @param node
*/
MdChipCtrl.prototype.selectNodeContents = function(node) {
var range, selection;
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(node);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
}
};


/**
* Presents an input element to edit the contents of the chip.
*/
MdChipCtrl.prototype.goInEditMode = function() {
this.isEditting = true;
this.$element.addClass('_md-chip-editing');
this.getChipContent()[0].contentEditable = 'true';
this.getChipContent().on('blur', function() {
this.goOutOfEditMode();
}.bind(this));

this.selectNodeContents(this.getChipContent()[0]);
};


/**
* Handles the keydown event on the chip element. If enable-chip-edit attribute is
* set to true, space or enter keys can trigger going into edit mode. Enter can also
* trigger submitting if the chip is already being edited.
* @param event
*/
MdChipCtrl.prototype.chipKeyDown = function(event) {
if (!this.isEditting &&
(event.keyCode === this.$mdConstant.KEY_CODE.ENTER ||
event.keyCode === this.$mdConstant.KEY_CODE.SPACE)) {
event.preventDefault();
this.goInEditMode();
} else if (this.isEditting &&
event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {
event.preventDefault();
this.goOutOfEditMode();
}
};


/**
* Handles the double click event
*/
MdChipCtrl.prototype.chipMouseDown = function() {
if(this.getChipIndex() == this.parentController.selectedChip &&
this.enableChipEdit &&
!this.isEditting) {
this.goInEditMode();
}
};
25 changes: 16 additions & 9 deletions src/components/chips/js/chipDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,37 @@ var DELETE_HINT_TEMPLATE = '\
* MDChip Directive Definition
*
* @param $mdTheming
* @param $mdInkRipple
* @param $mdUtil
* @ngInject
*/
function MdChip($mdTheming, $mdUtil) {
var hintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);

return {
restrict: 'E',
require: '^?mdChips',
compile: compile
require: ['^?mdChips', 'mdChip'],
compile: compile,
controller: 'MdChipCtrl'
};

function compile(element, attr) {
// Append the delete template
element.append($mdUtil.processTemplate(hintTemplate));

return function postLink(scope, element, attr, ctrl) {
return function postLink(scope, element, attr, ctrls) {
var chipsController = ctrls.shift();
var chipController = ctrls.shift();
$mdTheming(element);

if (ctrl) angular.element(element[0].querySelector('._md-chip-content'))
.on('blur', function () {
ctrl.resetSelectedChip();
ctrl.$scope.$applyAsync();
});
if (chipsController) {
chipController.init(chipsController);

angular.element(element[0].querySelector('._md-chip-content'))
.on('blur', function () {
chipsController.selectedChip = -1;
chipsController.$scope.$applyAsync();
});
}
};
}
}
30 changes: 29 additions & 1 deletion src/components/chips/js/chipsController.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ angular
* @param $mdConstant
* @param $log
* @param $element
* @param $mdUtil
* @constructor
*/
function MdChipsCtrl ($scope, $mdConstant, $log, $element, $timeout) {
function MdChipsCtrl ($scope, $mdConstant, $log, $element, $timeout, $mdUtil) {
/** @type {$timeout} **/
this.$timeout = $timeout;

Expand Down Expand Up @@ -50,6 +51,8 @@ function MdChipsCtrl ($scope, $mdConstant, $log, $element, $timeout) {
/** @type {boolean} */
this.hasAutocomplete = false;

/** @type {string} */
this.enableChipEdit = $mdUtil.parseAttributeBoolean(this.mdEnableChipEdit);

/**
* Hidden hint text for how to delete a chip. Used to give context to screen readers.
Expand Down Expand Up @@ -146,13 +149,38 @@ MdChipsCtrl.prototype.inputKeydown = function(event) {
}
};


/**
* Updates the content of the chip at given index
* @param chipIndex
* @param chipContents
*/
MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
if(chipIndex >= 0 && chipIndex < this.items.length) {
this.items[chipIndex] = chipContents;
this.ngModelCtrl.$setDirty();
}
};


/**
* Returns true if a chip is currently being edited. False otherwise.
* @return {boolean}
*/
MdChipsCtrl.prototype.isEditingChip = function(){
return !!this.$element[0].getElementsByClassName('_md-chip-editing').length;
};


/**
* Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
* keys switch which chips is active
* @param event
*/
MdChipsCtrl.prototype.chipKeydown = function (event) {
if (this.getChipBuffer()) return;
if (this.isEditingChip()) return;

switch (event.keyCode) {
case this.$mdConstant.KEY_CODE.BACKSPACE:
case this.$mdConstant.KEY_CODE.DELETE:
Expand Down
4 changes: 4 additions & 0 deletions src/components/chips/js/chipsDirective.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
* @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
* the input and delete buttons. If no `ng-model` is provided, the chips will automatically be
* marked as readonly.
* @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can
* go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only
* supported for chips with basic template.
* @param {number=} md-max-chips The maximum number of chips allowed to add through user input.
* <br/><br/>The validation property `md-max-chips` can be used when the max chips
* amount is reached.
Expand Down Expand Up @@ -195,6 +198,7 @@
scope: {
readonly: '=readonly',
placeholder: '@',
mdEnableChipEdit: '@',
secondaryPlaceholder: '@',
maxChips: '@mdMaxChips',
transformChip: '&mdTransformChip',
Expand Down