Skip to content

Commit

Permalink
Merge pull request #1 from Group-One-Technology/jr.multiple-select
Browse files Browse the repository at this point in the history
Feature: Multiple Select
  • Loading branch information
jorenrui authored Nov 7, 2023
2 parents 6674109 + 2022f91 commit 0312ba6
Show file tree
Hide file tree
Showing 6 changed files with 597 additions and 167 deletions.
50 changes: 42 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ <h3 class="font-bold font-mono">Active State Example:</h3>
SHOW CODE
</button>
</div>
<div :load="activeTab = 'My Account';">
<script>
activeTab = 'Company';
</script>
<div>
<div class="hidden sm:block">
<nav class="isolate flex divide-x divide-gray-200 rounded-lg shadow" aria-label="Tabs">
<!-- Current: "text-gray-900", Default: "text-gray-500 hover:text-gray-700" -->
Expand Down Expand Up @@ -198,6 +201,9 @@ <h2 class="ml-2 text-sm font-semibold font-mono" :text="checkboxValue ? 'ON' : '
</div>
</div>

<script>
showSelect = false;
</script>
<div class="p-4 border border-dashed border-black rounded mt-2">
<div class="flex items-end">
<div class="flex flex-col w-full">
Expand Down Expand Up @@ -333,20 +339,41 @@ <h3 class="font-bold font-mono">Multi Select:</h3>

<div>
<script>
allTags = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4'];
allTags = ['Cherries', 'Chocolate', 'Blueberry', 'Vanilla'];
filteredTags = [];
selectedTags = [];
selectedTag = null;
searchQuery = '';
</script>

<div>
<input type="hidden" :value="selectedTags">

<input type="text" class="w-full py-2 px-2" :change="filteredTags = allTags.search(this.value)" :enter="selectedTags.add(filteredTags.first); filteredTags.remove(filteredTags.first)" />

<div :clickout="filteredTags = []" :class="filteredTags.length ? '' : 'hidden' " class="bg-white rounded">
<input
type="text"
class="w-full py-2 px-2"
:change="searchQuery = this.value;
filteredTags = allTags.subtract(selectedTags).search(searchQuery);
selectedTag = filteredTags.first"
:enter="selectedTags = selectedTags.add(selectedTag);
filteredTags = filteredTags.remove(selectedTag);
selectedTag = filteredTags.first"
:keyup.up="selectedTag = filteredTags.previousOf(selectedTag)"
:keyup.down="selectedTag = filteredTags.nextOf(selectedTag)"
/>

<div :clickout="filteredTags = []; selectedTag = null" :class="filteredTags.length ? '' : 'hidden' " class="bg-white rounded">
<p class="p-3 font-mono font-semibold mb-2 pb-1 border-b-2 border-gray-500 border-dashed">Search Result:</p>
<div :each="tag in filteredTags">
<div :click="selectedTags.add(tag); this.classList.add('hidden')" class="text-gray-700 font-mono font-semibold py-2 px-3 rounded hover:bg-blue-100 hover:text-blue-700">
<span :text="tag" ></span>
<div
:click="selectedTags = selectedTags.add(tag);
filteredTags = filteredTags.remove(selectedTag);
selectedTag = filteredTags.first"
:mouseover="selectedTag = tag"
:class="selectedTag == tag ? 'bg-blue-100 text-blue-700' : 'text-gray-700'"
class="font-mono font-semibold py-2 px-3 rounded cursor-pointer hover:bg-blue-100 hover:text-blue-700"
>
<span :text="tag"></span>
</div>
</div>
</div>
Expand All @@ -356,7 +383,14 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
<div class="flex items-center space-x-2" :each="tag in selectedTags">
<div class="flex items-center py-0.5 px-2 rounded-full bg-blue-100 text-blue-700" >
<span :text="tag" class="font-semibold font-mono text-xs"></span>
<span class="text-xs text-blue-800 font-semibold ml-2 cursor-pointer" :click="selectedTags.remove(tag)">x</span>
<span
class="px-2 py-1 text-xs text-blue-800 font-semibold ml-2 cursor-pointer rounded-full hover:bg-red-200 hover:text-red-600"
:click="selectedTags = selectedTags.remove(tag);
filteredTags = allTags.subtract(selectedTags).search(searchQuery);
selectedTag = filteredTags.first"
>
x
</span>
</div>
</div>
</div>
Expand Down
73 changes: 73 additions & 0 deletions lib/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export default class MiniArray extends Array {
constructor(...args) {
super(...args);
}

get first() {
return this[0];
}

get last() {
return this.at(-1);
}

nextOf(item) {
const nextIndex = this.indexOf(item) + 1;
return nextIndex >= this.length ? this.first : this.at(nextIndex);
}

previousOf(item) {
const previousIndex = this.indexOf(item) - 1;
return previousIndex < 0 ? this.last : this.at(previousIndex);
}

toggle(value) {
let newValue;

if (Array.isArray(value)) {
newValue = value;
} else {
let index = this.indexOf(value);
if (index === -1) {
newValue = this.concat(value);
} else {
newValue = this.slice(0, index).concat(this.slice(index + 1));
}
}

return new MiniArray(...newValue);
}

add(value) {
let index = this.indexOf(value);
if (index !== -1) return this;

const newValue = this.concat(value);

return new MiniArray(...newValue);
}

remove(value) {
let index = this.indexOf(value);
if (index === -1) return this;

const newValue = this.slice(0, index).concat(this.slice(index + 1));

return new MiniArray(...newValue);
}

subtract(arr) {
return new MiniArray(...this.filter(item => !arr.includes(item)));
}

search(query) {
const normalizedQuery = query.toLowerCase().split(/\s+/);

const matches = this.filter(item => {
const lowercaseItem = item.toLowerCase();
return normalizedQuery.every(word => lowercaseItem.includes(word));
});

return new MiniArray(...matches);
}
}
101 changes: 53 additions & 48 deletions lib/entity.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Lexer from './lexer'

export default class Entity {
constructor(el) {
Expand Down Expand Up @@ -68,7 +69,13 @@ export default class Entity {
const eventsSet = new Set(allMainEvents);
const attributeNames = Array.from(this.element.attributes).map(attr => attr.name);

const intersections = attributeNames.filter(value => eventsSet.has(value));
const intersections = attributeNames.filter((value) => {
if (eventsSet.has(value)) return true;
if (!value.startsWith(":")) return false;

const nativeEventName = `on${value.substring(1)}`;
return eventsSet.has(nativeEventName);
});

return intersections
}
Expand All @@ -82,29 +89,20 @@ export default class Entity {
return this._sanitizeExpression(attrVal)
}

// TODO: Don't replace var declarations
_sanitizeExpression(expr) {
// Add proxyWindow
this.variables.forEach(variable => {
const exp = expr.split(";").find(x => x.includes(variable))
if (exp){
if (exp.includes('el.')) {
// Pre process hte expr just to get the newValue but not saving it to the actual variable just yet
window['temp'] = eval(`proxyWindow['${this.parent.uuid}']`)
let tempExpr = exp
tempExpr = tempExpr.replaceAll(variable, `temp['${variable.replace("el.", "")}']`)
eval(tempExpr)
const newVal = JSON.stringify(window['temp'])

const newExpr = exp.replace(exp, `proxyWindow.${this.parent.uuid} = ${newVal};`)
expr = expr.replace(exp, newExpr)
} else {
expr = expr.replace(variable, `proxyWindow.${variable}`)
}
}
const lexer = new Lexer(expr)

lexer.replace(Lexer.TOKEN.reservedWord, 'this', 'this.element')

if (this.parent)
lexer.replace(Lexer.TOKEN.identifier, 'el', `proxyWindow['${this.parent.uuid}']`)

this.variables.forEach((variable) => {
lexer.replace(Lexer.TOKEN.identifier, variable, `proxyWindow.${variable}`)
})

expr = expr.replace("this", "this.element")
return expr
return lexer.output()
}

_sanitizeContentExpression(expr) {
Expand Down Expand Up @@ -153,18 +151,27 @@ export default class Entity {
}
}

evaluateLoadEvents() {
const loadExpr = this.element.getAttribute(":load");
if (!loadExpr) return;
this.evaluateEventAction(":load");
}

evaluateEach() {
const eachExpr = this.element.getAttribute(":each");

if (eachExpr) {
const [variable, iterable] = eachExpr.split(' in ');
const [args, iterable] = eachExpr.split(' in ');
const [variable, indexName] = args.split(',').map(v => v.trim());
const items = eval(iterable);
this.childClone ||= this.element.innerHTML

let newHTML = ''

items.forEach(item => {
newHTML += this.childClone.replaceAll(variable, `'${item}'`)
items.forEach((item, index) => {
newHTML += this.childClone
.replaceAll(indexName, index)
.replaceAll(variable, `'${item}'`);
})

this.element.innerHTML = newHTML
Expand Down Expand Up @@ -227,12 +234,6 @@ export default class Entity {
}
})

if (el.hasAttribute(":click")) {
el.addEventListener("click", (e) => {
this.evaluateEventAction(":click")
});
}

// Change binding
if (el.hasAttribute(":change")) {
if (el.type == "checkbox" || el.tagName == "select") {
Expand All @@ -254,28 +255,32 @@ export default class Entity {
});
}

if (el.hasAttribute(":keypress")) {
el.addEventListener("keypress", (e) => {
this.evaluateEventAction(":keypress")
});
}

if (el.hasAttribute(":keydown")) {
el.addEventListener("keydown", (e) => {
this.evaluateEventAction(":keydown")
if (el.hasAttribute(":clickout")) {
document.addEventListener("click", (e) => {
if (!document.body.contains(e.target)) return;
if (el.contains(e.target)) return;
this.evaluateEventAction(":clickout")
});
}

// Other Event Bindings
Array.from(el.attributes).forEach((attr) => {
if (attr.name.startsWith(":") && !MiniJS.allCustomBindings.includes(attr.name)) {
const nativeEventName = attr.name.substring(1);
el.addEventListener(nativeEventName, (e) => {
this.evaluateEventAction(attr.name);
});
} else if (attr.name.startsWith(":keyup")) {
const [event, direction] = attr.name.split(".");
if (!["up", "down", "left", "right"].includes(direction)) return;

if (el.hasAttribute(":keyup")) {
el.addEventListener("keyup", (e) => {
this.evaluateEventAction(":keyup")
});
}
const key = "Arrow" + direction[0].toUpperCase() + direction.slice(1);
const nativeEventName = event.substring(1);

document.addEventListener('click', (e) => {
if (el.hasAttribute(":clickout") && !el.contains(e.target))
{
this.evaluateEventAction(":clickout")
el.addEventListener(nativeEventName, (e) => {
if (e.key !== key) return;
this.evaluateEventAction(attr.name);
});
}
});
}
Expand Down
Loading

0 comments on commit 0312ba6

Please sign in to comment.