Skip to content

Commit

Permalink
fix(javascript): avoid internal error by missing elements in HTML (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
asudoh authored and chrisdhanaraj committed Apr 25, 2017
1 parent 18c7418 commit 95f3aee
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 69 deletions.
10 changes: 6 additions & 4 deletions src/components/copy-button/copy-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ class CopyButton extends mixin(createComponent, InitComponentBySearch) {
*/
handleClick() {
const feedback = this.element.querySelector(this.options.feedbackTooltip);
feedback.classList.add(this.options.classShowFeedback);
setTimeout(() => {
feedback.classList.remove(this.options.classShowFeedback);
}, this.options.timeoutValue);
if (feedback) {
feedback.classList.add(this.options.classShowFeedback);
setTimeout(() => {
feedback.classList.remove(this.options.classShowFeedback);
}, this.options.timeoutValue);
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/components/data-table/data-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class DataTable extends mixin(createComponent, initComponentBySearch, eventedSta
this.parentRows = [...this.element.querySelectorAll(this.options.selectorParentRows)];
this.tableBody = this.element.querySelector(this.options.selectorTableBody);

if (!this.tableBody) {
throw new Error('Cannot find the table body.');
}

this._zebraStripe();
this._initExpandableRows();

Expand Down
10 changes: 9 additions & 1 deletion src/components/file-uploader/file-uploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ class FileUploader extends mixin(createComponent, initComponentBySearch, evented
constructor(element, options = {}) {
super(element, options);
this.input = this.element.querySelector(this.options.selectorInput);
this.inputId = this.input.getAttribute('id');
this.container = this.element.querySelector(this.options.selectorContainer);

if (!this.input) {
throw new Error('Cannot find the file input box.');
}

if (!this.container) {
throw new Error('Cannot find the file names container.');
}

this.inputId = this.input.getAttribute('id');
this.input.addEventListener('change', () => this._displayFilenames());
}

Expand Down
14 changes: 8 additions & 6 deletions src/components/notification/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ class Notification extends mixin(createComponent, initComponentBySearch, evented
*/
constructor(element, options) {
super(element, options);
this.button = element.querySelector(this.options.selectorButton) || false;
this.button.addEventListener('click', (evt) => {
if (evt.currentTarget === this.button) {
this.remove();
}
});
this.button = element.querySelector(this.options.selectorButton);
if (this.button) {
this.button.addEventListener('click', (evt) => {
if (evt.currentTarget === this.button) {
this.remove();
}
});
}
}

_changeState = (state, callback) => {
Expand Down
3 changes: 3 additions & 0 deletions src/components/search/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class Search extends mixin(createComponent, initComponentBySearch) {
super(element, options);
const closeIcon = this.element.querySelector(this.options.selectorClearIcon);
const input = this.element.querySelector(this.options.selectorSearchInput);
if (!input) {
throw new Error('Cannot find the search input.');
}

if (closeIcon) {
closeIcon.addEventListener('click', () => {
Expand Down
10 changes: 8 additions & 2 deletions src/components/tabs/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,21 @@ class Tab extends ContentSwitcher {
* Shows/hides the drop down menu used in narrow mode.
*/
_updateMenuState() {
this.element.querySelector(this.options.selectorMenu).classList.toggle(this.options.classHidden);
const menu = this.element.querySelector(this.options.selectorMenu);
if (menu) {
menu.classList.toggle(this.options.classHidden);
}
}

/**
* Updates the text indicating the currently selected tab item.
* @param {HTMLElement} target The newly selected tab item.
*/
_updateTriggerText(target) {
this.element.querySelector(this.options.selectorTriggerText).textContent = target.textContent;
const triggerText = this.element.querySelector(this.options.selectorTriggerText);
if (triggerText) {
triggerText.textContent = target.textContent;
}
}

/**
Expand Down
34 changes: 19 additions & 15 deletions src/components/toolbar/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ class Toolbar extends mixin(createComponent, initComponentBySearch) {
} else {
const boundTable = this.element.ownerDocument.querySelector(this.element.dataset.tableTarget);
const rowHeightBtns = this.element.querySelector(this.options.selectorRowHeight);

rowHeightBtns.addEventListener('click', (event) => { this._handleRowHeightChange(event, boundTable); });
// [...this.element.querySelectorAll(this.options.selectorRowHeight)].forEach((item) => {
// item.addEventListener('click', (event) => { this._handleRowHeightChange(event, boundTable); });
// });
if (rowHeightBtns) {
rowHeightBtns.addEventListener('click', (event) => { this._handleRowHeightChange(event, boundTable); });
// [...this.element.querySelectorAll(this.options.selectorRowHeight)].forEach((item) => {
// item.addEventListener('click', (event) => { this._handleRowHeightChange(event, boundTable); });
// });
}
}

this.hDocumentKeyDown = on(this.element.ownerDocument, 'keydown', (evt) => { this._handleKeyDown(evt); });
Expand All @@ -37,19 +38,22 @@ class Toolbar extends mixin(createComponent, initComponentBySearch) {
*/
_handleDocumentClick(event) {
const searchInput = eventMatches(event, this.options.selectorSearch);
const isOfSelf = this.element.querySelector(this.options.selectorSearch).contains(event.target);
const isOfToolbar = this.element.contains(event.target);
const shouldBeOpen = isOfSelf && !this.element.classList.contains(this.options.classSearchActive);
const isOfSelfSearchInput = searchInput && this.element.contains(searchInput);

if (searchInput && shouldBeOpen) {
searchInput.classList.add(this.options.classSearchActive);
searchInput.querySelector('input').focus();
if (isOfSelfSearchInput) {
const shouldBeOpen = isOfSelfSearchInput && !this.element.classList.contains(this.options.classSearchActive);
searchInput.classList.toggle(this.options.classSearchActive, shouldBeOpen);
if (shouldBeOpen) {
searchInput.querySelector('input').focus();
}
}
if (!searchInput && !isOfToolbar) {
[...this.element.ownerDocument.querySelectorAll(this.options.selectorSearch)].forEach((item) => {

const targetComponentElement = eventMatches(event, this.options.selectorInit);
[...this.element.ownerDocument.querySelectorAll(this.options.selectorSearch)].forEach((item) => {
if (!targetComponentElement || !targetComponentElement.contains(item)) {
item.classList.remove(this.options.classSearchActive);
});
}
}
});
}

/**
Expand Down
103 changes: 62 additions & 41 deletions src/components/unified-header/profile-switcher.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mixin from '../../globals/js/misc/mixin';
import createComponent from '../../globals/js/mixins/create-component';
import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
import eventMatches from '../../globals/js/misc/event-matches';
import on from '../../globals/js/misc/on';

class ProfileSwitcher extends mixin(createComponent, initComponentBySearch) {
Expand Down Expand Up @@ -31,29 +32,28 @@ class ProfileSwitcher extends mixin(createComponent, initComponentBySearch) {

this.element.addEventListener('dropdown-beingselected', (event) => {
if (event.target.querySelector(this.options.selectorAccountDropdown) !== null) {
if (event.detail.item.querySelector(this.options.classLinkedIcon) !== null) {
this.element.linkedAccount = event.detail.item.querySelector(this.options.selectorAccountSlLinked).cloneNode(true);
this.element.isLinked = true;
this.element.linkedIcon = event.detail.item.querySelector(this.options.classLinkedIcon).cloneNode(true);
} else {
this.element.linkedAccount = '';
this.element.isLinked = false;
this.element.linkedIcon = '';
}
const linkedIconNode = event.detail.item.querySelector(this.options.classLinkedIcon);
this.element.isLinked = !!linkedIconNode;
this.element.linkedIcon = linkedIconNode && linkedIconNode.cloneNode(true);
const linkedAccountNode = event.detail.item.querySelector(this.options.selectorAccountSlLinked);
this.element.linkedAccount = linkedAccountNode && linkedAccountNode.cloneNode(true);
}
});

this.element.querySelector(this.options.selectorToggle).addEventListener('keydown', (event) => { this.toggle(event); });
const toggleNode = this.element.querySelector(this.options.selectorToggle);
if (toggleNode) {
toggleNode.addEventListener('keydown', (event) => { this.toggle(event); });

this.element.querySelector(this.options.selectorToggle).addEventListener('mouseenter', (event) => {
this.getLinkedData(event);
this.determineSwitcherValues(true);
});
toggleNode.addEventListener('mouseenter', (event) => {
this.getLinkedData(event);
this.determineSwitcherValues(true);
});

this.element.querySelector(this.options.selectorToggle).addEventListener('mouseleave', (event) => {
this.getLinkedData(event);
this.determineSwitcherValues(false);
});
toggleNode.addEventListener('mouseleave', (event) => {
this.getLinkedData(event);
this.determineSwitcherValues(false);
});
}

this.element.ownerDocument.addEventListener('keyup', () => this.handleBlur());
}
Expand Down Expand Up @@ -98,7 +98,7 @@ class ProfileSwitcher extends mixin(createComponent, initComponentBySearch) {
handleDocumentClick(evt) {
const clickTarget = evt.target;
const isOfSelf = this.element.contains(clickTarget);
const isToggle = this.element.ownerDocument.querySelector(this.options.selectorToggle).contains(clickTarget);
const isToggle = eventMatches(evt, this.options.selectorToggle);
const isOpen = this.element.classList.contains(this.options.classSwitcherOpen);

if (isOfSelf) {
Expand Down Expand Up @@ -178,35 +178,56 @@ class ProfileSwitcher extends mixin(createComponent, initComponentBySearch) {
let spaceShort;

if (isHovered && !isOpen) {
nameElement.textContent = nameDropdownValue;
orgElement.textContent = orgDropdownValue;
spaceElement.textContent = spaceDropdownValue;
regionElement.textContent = regionDropdownValue;
menuElement.style.width = `${this.element.getBoundingClientRect().width}px`;
} else {
if (nameDropdownValue.length > 25) {
nameShort = `${nameDropdownValue.substr(0, 25)}...`;
nameElement.textContent = nameShort;
} else {
if (nameElement) {
nameElement.textContent = nameDropdownValue;
}

if (orgDropdownValue.length > 25) {
orgShort = `${orgDropdownValue.slice(0, 12)}...${orgDropdownValue.slice(-13)}`;
orgElement.textContent = orgShort;
} else {
if (orgElement) {
orgElement.textContent = orgDropdownValue;
}

if (spaceDropdownValue.length > 25) {
spaceShort = `${spaceDropdownValue.substr(0, 25)}...`;
spaceElement.textContent = spaceShort;
} else {
if (spaceElement) {
spaceElement.textContent = spaceDropdownValue;
}
if (regionElement) {
regionElement.textContent = regionDropdownValue;
}
if (menuElement) {
menuElement.style.width = `${this.element.getBoundingClientRect().width}px`;
}
} else {
if (nameElement) {
if (nameDropdownValue.length > 25) {
nameShort = `${nameDropdownValue.substr(0, 25)}...`;
nameElement.textContent = nameShort;
} else {
nameElement.textContent = nameDropdownValue;
}
}

if (orgElement) {
if (orgDropdownValue.length > 25) {
orgShort = `${orgDropdownValue.slice(0, 12)}...${orgDropdownValue.slice(-13)}`;
orgElement.textContent = orgShort;
} else {
orgElement.textContent = orgDropdownValue;
}
}

regionElement.textContent = regionDropdownValue;
menuElement.style.width = `${this.element.getBoundingClientRect().width}px`;
if (spaceElement) {
if (spaceDropdownValue.length > 25) {
spaceShort = `${spaceDropdownValue.substr(0, 25)}...`;
spaceElement.textContent = spaceShort;
} else {
spaceElement.textContent = spaceDropdownValue;
}
}

if (regionElement) {
regionElement.textContent = regionDropdownValue;
}

if (menuElement) {
menuElement.style.width = `${this.element.getBoundingClientRect().width}px`;
}
}
}

Expand Down
44 changes: 44 additions & 0 deletions tests/spec/toolbar_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,48 @@ describe('Test accordion', function () {
}
});
});

describe('Exclusive search box', function () {
let container;
const toolbars = [];

before(function () {
container = document.createElement('div');
container.innerHTML = ToolbarHTML + ToolbarHTML;
document.body.appendChild(container);
toolbars.push(...[...container.querySelectorAll('[data-toolbar]')].map(elem => new Toolbar(elem)));
});

beforeEach(function () {
toolbars.forEach((toolbar) => {
toolbar.element.querySelector(toolbar.options.selectorSearch).classList.remove(toolbar.classSearchActive);
});
});

it('Should make the search box exclusive upon clicking on one of the search boxes', function () {
const searches = toolbars.map(toolbar => toolbar.element.querySelector(toolbar.options.selectorSearch));
searches[0].classList.add(toolbars[0].classSearchActive);
searches[1].dispatchEvent(new CustomEvent('click', { bubbles: true }));
expect(searches[0].classList.contains('bx--toolbar-search--active')).to.be.false;
expect(searches[1].classList.contains('bx--toolbar-search--active')).to.be.true;
});

it('Should make the search box exclusive upon hitting space bar on one of the search boxes', function () {
const searches = toolbars.map(toolbar => toolbar.element.querySelector(toolbar.options.selectorSearch));
searches[0].classList.add(toolbars[0].classSearchActive);
searches[1].dispatchEvent(Object.assign(new CustomEvent('keydown', { bubbles: true }), { which: 32 }));
expect(searches[0].classList.contains('bx--toolbar-search--active')).to.be.false;
expect(searches[1].classList.contains('bx--toolbar-search--active')).to.be.true;
});

after(function () {
for (let toolbar = toolbars.pop(); toolbar; toolbar = toolbars.pop()) {
toolbar.release();
}
if (document.body.contains(container)) {
document.body.removeChild(container);
container = null;
}
});
});
});

0 comments on commit 95f3aee

Please sign in to comment.