Skip to content

Commit

Permalink
refactor(autocomplete): merge branch
Browse files Browse the repository at this point in the history
Merge 'v2-dev' into component-types-docs
  • Loading branch information
mauromascarenhas committed Jun 22, 2023
2 parents c6cdb95 + 3dcfae8 commit 38210ae
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 43 deletions.
2 changes: 1 addition & 1 deletion docs/js/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ document.addEventListener("DOMContentLoaded", function() {

M.FormSelect.init(document.querySelectorAll('select:not(.disabled)'), {});

M.CharacterCounter.init(document.querySelectorAll('input[data-length], textarea[data-length]'), {});
M.CharacterCounter.init(document.querySelectorAll('input[maxlength], textarea[maxlength]'), {});

const autocompleteDemoData = [
{id: 12, text: "Apple"},
Expand Down
53 changes: 42 additions & 11 deletions pug/contents/autocomplete_content.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,38 @@

<div id="initialization" class="scrollspy section">
<h3 class="header">Initialization</h3>
<p>The data is a json object where the key is the matching string and the value is an optional image url.</p>
<p>The key must be a text string. If you trust your data, or have properly sanitized your user input, you may
use HTML by setting the option <code class="language-javascript">allowUnsafeHTML: true</code>.</p>
<p>The data is an array of option objects, which supports three different attributes:</p>
<p>
<ul class="collection">
<li class="collection-item">
<p style="margin: 0;">
<b>id</b>: This is the only mandatory attribute: it must be a primitive value that can be
converted to string. If "text" is not provided, it will also be used as "option text" as well;
</p>
</li>
<li class="collection-item">
<p style="margin: 0;">
<b>text</b>: This optional attribute is used as "display value" for the current entry.
When provided, it will also be taken into consideration by the standard search function.
</p>
<P style="margin: 0;">
If you trust your data or have properly sanitized your user input, you may use
use HTML by setting the option <code class="language-javascript">allowUnsafeHTML: true</code>;
</P>
</li>
<li class="collection-item">
<p style="margin: 0;">
<b>image</b>: This optional attribute is used to provide a valid image URL to the current option.
This attribute is ignored by the standard search function.
</p>
</li>
</ul>
</p>
<p>
You may also provide additional attributes to an option object but they will not be taken into
consideration by the standard search function. If you want to use them for option filtering, you
must specify a custom function in "<b>onSearch</b>" option.
</p>
<pre style="padding-top: 0px;">
<span class="copyMessage">Copied!</span>
<i class="material-icons copyButton">content_copy</i>
Expand Down Expand Up @@ -99,7 +128,8 @@ <h3 class="header">Initialization</h3>
{id: 13, text: "Microsoft"},
{id: 42, text: "Google", image: 'http://placehold.it/250x250'}
],
onSearch: function(text, autocomplete) {
// This search function considers every object entry as "search values".
onSearch: (text, autocomplete) => {
const filteredData = autocomplete.options.data.filter(item => {
return Object.keys(item)
.map(key => item[key].toString().toLowerCase().indexOf(text.toLowerCase()) >= 0)
Expand Down Expand Up @@ -188,13 +218,14 @@ <h5 class="method-header">
<span class="copyMessage">Copied!</span>
<i class="material-icons copyButton">content_copy</i>
<code class="language-javascript copiedText">
onSearch: function(text, autocomplete) {
const filteredData = autocomplete.options.data.filter(item => {
return Object.keys(item)
.map(key => item[key].toString().toLowerCase().indexOf(text.toLowerCase()) >= 0)
.some(isMatch => isMatch);
});
autocomplete.setMenuItems(filteredData);
onSearch: (text, autocomplete) => {
const normSearch = text.toLocaleLowerCase();
autocomplete.setMenuItems(
autocomplete.options.data.filter((option) =>
option.id.toString().toLocaleLowerCase().includes(normSearch)
|| option.text?.toLocaleLowerCase().includes(normSearch)
)
);
}
</code>
</pre>
Expand Down
71 changes: 54 additions & 17 deletions pug/contents/text_inputs_content.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,46 @@ <h3 class="header">Input fields</h3>
&lt;/form>
&lt;/div>
</code></pre>
<h5>Floating Labels with CSS only</h5>
<p>There should not be any problems with initializing the inputs properly, since the floating Labels are rendered with CSS only. The CSS Technique requires to use an empty placeholder in the HTML. You have to put an emtpy space in the placeholder. Putting an empty string wont work.</p>
<div class="input-field outline">
<input value="Daniel" id="first_name2" type="text" class="validate" placeholder=" ">
<label class="active" for="first_name2">First Name</label>
<h5 id="floating-labels">Floating Labels</h5>
<p>
Since <b>MaterializeCSS v2</b>, floating Labels are rendered with CSS only by default.
However, it is required to use a placeholder with a single white space (" ") in the HTML
(providing an empty string will not work!).
</p>
<p>
<strong><b>Important:</b></strong> If you provide a value different than a single white space,
the CSS rules will treat it as a "important" placeholder value and will always render the
labels in "active" state.
</p>
<div class="row">
<div class="col s12 m6">
<div class="input-field outlined" style="margin: 0 4px;">
<input id="first_name2" type="text" class="validate" placeholder=" ">
<label class="active" for="first_name2">First Name</label>
</div>
</div>
<div class="col s12 m6">
<div class="input-field outlined" style="margin: 0 4px;">
<input id="last_name2" type="text" class="validate" placeholder="Doe...">
<label class="active" for="last_name2">Last Name</label>
</div>
</div>
</div>

<pre><code class="language-markup">
&lt;div class="input-field">
&lt;input value="Daniel" id="first_name2" type="text" class="validate" placeholder=" ">
&lt;label class="active" for="first_name2">First Name&lt;/label>
&lt;div class="row">
&lt;div class="col s12 m6">
&lt;div class="input-field outlined" style="margin: 0 4px;">
&lt;input id="first_name2" type="text" class="validate" placeholder=" ">
&lt;label class="active" for="first_name2">First Name&lt;/label>
&lt;/div>
&lt;/div>
&lt;div class="col s12 m6">
&lt;div class="input-field outlined" style="margin: 0 4px;">
&lt;input id="last_name2" type="text" class="validate" placeholder="Doe...">
&lt;label class="active" for="last_name2">Last Name&lt;/label>
&lt;/div>
&lt;/div>
&lt;/div>
</code></pre>

Expand Down Expand Up @@ -308,10 +337,10 @@ <h3 class="header">Textarea</h3>
&lt;/form>
&lt;/div>
</code></pre>
<p>advanced note: When dynamically changing the value of a textarea with methods like jQuery's <code class="language-markup">.val()</code>, you must trigger an autoresize on it afterwords because .val() does not automatically trigger the events we've binded to the textarea. </p>
<p><strong><b>Advanced note:</b></strong> When dynamically changing the value of a textarea like setting <code class="language-markup">HTMLElement#value</code> attribute, you must trigger an autoresize on it afterwords because simply updating the element's value does not automatically trigger the events we've binded to the textarea.</p>
<pre><code class="language-javascript">
$('#textarea1').val('New Text');
M.textareaAutoResize($('#textarea1'));
document.querySelector("#textarea1").value = 'New Text';
M.Forms.textareaAutoResize(document.querySelector('#textarea1'));
</code></pre>


Expand Down Expand Up @@ -404,17 +433,24 @@ <h3 class="header">File Input</h3>
<div id="character-counter" class="section scrollspy">
<h3 class="header">Character Counter</h3>
<p class="caption">Use a character counter in fields where a character restriction is in place.</p>
<div class="row">
<div class="col s12">
<div class="card-panel yellow black-text">
<strong><b>Important:</b></strong> The "data-length" attribute has been depracated in favour of "maxlength" in v2.x.
</div>
</div>
</div>
<div class="row">
<form class="col s12">
<div class="row">
<div class="input-field col s6">
<input id="input_text" type="text" data-length="10">
<input id="input_text" type="text" maxlength="10">
<label for="input_text">Input text</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<textarea id="textarea2" class="materialize-textarea" data-length="120"></textarea>
<textarea id="textarea2" class="materialize-textarea" maxlength="120"></textarea>
<label for="textarea2">Textarea</label>
</div>
</div>
Expand All @@ -425,13 +461,13 @@ <h3 class="header">Character Counter</h3>
&lt;form class="col s12">
&lt;div class="row">
&lt;div class="input-field col s6">
&lt;input id="input_text" type="text" data-length="10">
&lt;input id="input_text" type="text" maxlength="10">
&lt;label for="input_text">Input text&lt;/label>
&lt;/div>
&lt;/div>
&lt;div class="row">
&lt;div class="input-field col s12">
&lt;textarea id="textarea2" class="materialize-textarea" data-length="120">&lt;/textarea>
&lt;textarea id="textarea2" class="materialize-textarea" maxlength="120">&lt;/textarea>
&lt;label for="textarea2">Textarea&lt;/label>
&lt;/div>
&lt;/div>
Expand All @@ -443,8 +479,9 @@ <h3 class="header">Character Counter</h3>
<h5>Initialization</h5>
<p>There are no options for this plugin, but if you are adding these dynamically, you can use this to initialize them.</p>
<pre><code class="language-javascript">
$(document).ready(function() {
$('input#input_text, textarea#textarea2').characterCounter();
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('input#input_text, textarea#textarea2');
var instances = M.CharacterCounter.init(elems);
});
</code></pre>
</div>
Expand Down
2 changes: 2 additions & 0 deletions sass/components/forms/_input-fields.scss
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ textarea.materialize-textarea {
color: $input-focus-color;
}
&:focus:not([readonly]) + label,
&:not([placeholder=' ']) + label,
&:not(:placeholder-shown) + label {
//font-size: 12px; // md.sys.typescale.body-small.size
// https://stackoverflow.com/questions/34717492/css-transition-font-size-avoid-jittering-wiggling
Expand Down Expand Up @@ -187,6 +188,7 @@ textarea.materialize-textarea {
color: $input-focus-color;
}
&:focus:not([readonly]) + label,
&:not([placeholder=' ']) + label,
&:not(:placeholder-shown) + label {
top: -8px;
left: 16px;
Expand Down
23 changes: 12 additions & 11 deletions src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ let _defaults: AutocompleteOptions = {
},
minLength: 1, // Min characters before autocomplete starts
isMultiSelect: false,
onSearch: function(text, autocomplete) {
const filteredData = autocomplete.options.data.filter(item => {
return Object.keys(item)
.map(key => item[key].toString().toLowerCase().indexOf(text.toLowerCase()) >= 0)
.some(isMatch => isMatch);
});
autocomplete.setMenuItems(filteredData);
onSearch: (text: string, autocomplete: Autocomplete) => {
const normSearch = text.toLocaleLowerCase();
autocomplete.setMenuItems(
autocomplete.options.data.filter((option) =>
option.id.toString().toLocaleLowerCase().includes(normSearch)
|| option.text?.toLocaleLowerCase().includes(normSearch)
)
);
},
maxDropDownHeight: '300px',
allowUnsafeHTML: false
Expand Down Expand Up @@ -269,7 +270,7 @@ export class Autocomplete extends Component<AutocompleteOptions> {
_handleInputKeyupAndFocus = (e: KeyboardEvent) => {
if (e.type === 'keyup') Autocomplete._keydown = false;
this.count = 0;
const actualValue = this.el.value.toLowerCase();
const actualValue = this.el.value.toLocaleLowerCase();
// Don't capture enter or arrow key usage.
if (Utils.keys.ENTER.includes(e.key) || Utils.keys.ARROW_UP.includes(e.key) || Utils.keys.ARROW_DOWN.includes(e.key)) return;
// Check if the input isn't empty
Expand Down Expand Up @@ -348,7 +349,7 @@ export class Autocomplete extends Component<AutocompleteOptions> {
}

_highlightPartialText(input: string, label: string) {
const start = label.toLowerCase().indexOf('' + input.toLowerCase() + '');
const start = label.toLocaleLowerCase().indexOf('' + input.toLocaleLowerCase() + '');
const end = start + input.length - 1;
//custom filters may return results where the string does not match any part
if (start == -1 || end == -1) {
Expand Down Expand Up @@ -382,7 +383,7 @@ export class Autocomplete extends Component<AutocompleteOptions> {
}

// Text
const inputText = this.el.value.toLowerCase();
const inputText = this.el.value.toLocaleLowerCase();
const parts = this._highlightPartialText(inputText, (entry.text || entry.id).toString());
const div = document.createElement('div');
div.setAttribute('style', 'line-height:1.2;font-weight:500;');
Expand Down Expand Up @@ -475,7 +476,7 @@ export class Autocomplete extends Component<AutocompleteOptions> {
* Show autocomplete.
*/
open = () => {
const inputText = this.el.value.toLowerCase();
const inputText = this.el.value.toLocaleLowerCase();
this._resetAutocomplete();
if (inputText.length >= this.options.minLength) {
this.isOpen = true;
Expand Down
2 changes: 1 addition & 1 deletion src/characterCounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class CharacterCounter extends Component<{}> {
}

updateCounter = () => {
let maxLength = parseInt(this.el.getAttribute('data-length')),
let maxLength = parseInt(this.el.getAttribute('maxlength')),
actualLength = (this.el as HTMLInputElement).value.length;

this.isValidLength = actualLength <= maxLength;
Expand Down
7 changes: 6 additions & 1 deletion src/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { Utils } from "./utils";

export class Forms {

static textareaAutoResize(textarea: HTMLTextAreaElement) {
/**
* Resizes the given TextArea after updating the
* value content dynamically.
* @param textarea TextArea to be resized
*/
static textareaAutoResize(textarea: HTMLTextAreaElement){
if (!textarea) {
console.error('No textarea element found');
return;
Expand Down
1 change: 1 addition & 0 deletions src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class M {
static Collapsible: typeof Collapsible = Collapsible;
static Datepicker: typeof Datepicker = Datepicker;
static CharacterCounter: typeof CharacterCounter = CharacterCounter;
static Forms: typeof Forms = Forms;
static FormSelect: typeof FormSelect = FormSelect;
static Modal: typeof Modal = Modal;
static Pushpin: typeof Pushpin = Pushpin;
Expand Down
8 changes: 8 additions & 0 deletions tests/spec/forms/formsFixture.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<label for="text-input">text</label>
<input id="text-input" type="text" />
</div>
<div class="input-field">
<label for="character-counter">character counter</label>
<input id="character-counter" type="text" maxlength="10" />
</div>
<div class="input-field">
<label for="no-type-attribute">no type attribute</label>
<input id="no-type-attribute" />
Expand Down Expand Up @@ -46,3 +50,7 @@
<label for="datetime-input">datetime</label>
<input id="datetime-input" type="datetime-local" />
</div>
<div class="input-field">
<textarea id="textarea" class="materialize-textarea"></textarea>
<label for="textarea">Textarea</label>
</div>
35 changes: 34 additions & 1 deletion tests/spec/forms/formsSpec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
describe('Forms:', function() {
beforeEach(async function() {
await XloadFixtures(['forms/formsFixture.html']);
M.CharacterCounter.init(document.querySelector("#character-counter"));
});

afterEach(function(){
Expand All @@ -11,10 +12,42 @@ describe('Forms:', function() {

beforeEach(function() {
inputs = document.querySelectorAll('input');
inputs.forEach(input => input.blur())
inputs.forEach((input) => {
input.focus();
input.blur();
});
window.location.hash = "";
});

describe("CharacterCounter", () => {
it("Should initialize", () => {
let el = document.querySelector("#character-counter");
expect(() => M.CharacterCounter.getInstance(el)).not.toThrow();
expect(M.CharacterCounter.getInstance(el)).toBeTruthy();
});

it("Should exhibit counter", () => {
let counter = document.querySelector("#character-counter ~ .character-counter");
expect(counter.textContent).toBe("0/10");
});
});

describe('TextArea Resize', () => {
it("Should resize", () => {
const el = document.querySelector("#textarea");
const pHeight = el.clientHeight;
el.value = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eleifend urna orci, vitae sagittis ligula maximus quis. Duis eleifend ipsum vitae facilisis tincidunt. Aliquam condimentum consequat ex, ut commodo purus tristique at. Donec malesuada fringilla libero vel sodales. Nulla finibus volutpat lectus a varius. Praesent consequat ornare pulvinar. Quisque nec massa diam.
Nunc commodo tempus suscipit. Phasellus iaculis at lorem sit amet venenatis. Curabitur quis felis elementum enim fermentum dapibus. In pretium finibus mollis. Nam aliquet tristique diam sit amet ullamcorper. Suspendisse interdum, est sed aliquam dignissim, dolor augue tristique dui, non luctus felis dolor a dui. Suspendisse lacinia lorem nec enim ultricies maximus. Aenean quam erat, finibus non aliquam nec, pharetra vel metus. Nulla dignissim maximus cursus.
Integer massa est, semper eget sem quis, bibendum scelerisque odio. Nam sit amet urna auctor, luctus odio in, semper dui. Sed ut gravida libero, ac consectetur sem. Etiam pharetra pulvinar leo, eget imperdiet purus faucibus in. Cras blandit mi ullamcorper nulla viverra posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec pretium euismod tortor a lacinia. Vivamus ultrices vulputate purus et blandit. Fusce mi quam, consequat vitae pretium sed, tempus at ligula.
Suspendisse sodales et dolor vitae sollicitudin. Curabitur sed vestibulum sapien. Integer porttitor pulvinar ullamcorper. Sed ultrices varius augue, at bibendum magna congue sit amet. Nam enim purus, fermentum sed feugiat viverra, accumsan nec diam. Donec a auctor est. Aenean non ante metus. Pellentesque ante ligula, varius vel dignissim in, euismod vel diam. Donec est ante, rhoncus at eros sed, cursus pulvinar enim. In pellentesque, erat eu egestas tempor, ipsum turpis ornare dui, sed fringilla sem lorem in ligula.
Integer facilisis arcu eu posuere placerat. Nam vel leo magna. Proin mattis feugiat nisi, quis tincidunt magna pulvinar tincidunt. Aliquam eget nunc sapien. Maecenas vitae orci nunc. Nulla condimentum sapien quis sapien varius suscipit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus non finibus nisl, et venenatis massa.
`.trim();
M.Forms.textareaAutoResize(el);
expect(el.clientHeight).toBeGreaterThan(pHeight);
});
});

// No active class added, because it is now a css feature only
/*
it("should keep label active while focusing on input", function () {
Expand Down

0 comments on commit 38210ae

Please sign in to comment.