mirror of
https://github.com/Karaka-Management/jsOMS.git
synced 2026-01-11 09:58:39 +00:00
prepare more input fields and better markdown/content editing
This commit is contained in:
parent
2af8d9995f
commit
042feca3b2
0
UI/Component/AdvancedRange.js
Normal file
0
UI/Component/AdvancedRange.js
Normal file
|
|
@ -1,6 +1,326 @@
|
|||
// remote data
|
||||
// select data could be template layout per element
|
||||
// multi select
|
||||
// select with search feature for many options
|
||||
|
||||
// isn't this very similar to the advanced input? just a little different?
|
||||
// maybe not...
|
||||
// maybe not...
|
||||
|
||||
import { Request } from '../../Message/Request/Request.js';
|
||||
/**
|
||||
* Advanced input class.
|
||||
*
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 1.0
|
||||
* @version 1.0.0
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo: this class is probably the most stupid thing I've done in a long time. Seriously fix this!
|
||||
* @todo: Passing self to every MEMBER function is just dumb.
|
||||
*/
|
||||
export class AdvancedSelect {
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} e Element to bind
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
constructor(e) {
|
||||
this.id = e.id;
|
||||
this.selectComponent = e;
|
||||
this.selectField = this.selectComponent.getElementsByClassName('input')[0];
|
||||
this.dropdownElement = document.getElementById(this.id + '-dropdown');
|
||||
this.tagElement = document.getElementById(this.id + '-tags');
|
||||
this.dataList = this.dropdownElement.getElementsByTagName('table')[0];
|
||||
this.dataListBody = this.dataList.getElementsByTagName('tbody')[0];
|
||||
this.dataTpl = document.getElementById(this.id + '-rowElement');
|
||||
this.tagTpl = this.tagElement.getElementsByTagName('template')[0];
|
||||
this.src = this.selectField.getAttribute('data-src');
|
||||
|
||||
const self = this;
|
||||
this.selectField.addEventListener('focusout', function (e) {
|
||||
// todo: this also means that clicking on any other part of the result list that it disappears befor
|
||||
// the click is registered in the result list since focusout has highest priority (e.g. sort button in table).
|
||||
// so far i don't know a way to check if *any* element in the result div is clicked, if I could check this
|
||||
// first then I could simply say, don't make the result div inactive!
|
||||
if (e.relatedTarget === null ||
|
||||
e.relatedTarget.parentElement === null ||
|
||||
e.relatedTarget.parentElement.parentElement === null ||
|
||||
!jsOMS.hasClass(e.relatedTarget.parentElement.parentElement.parentElement, 'dropdown')
|
||||
) {
|
||||
jsOMS.removeClass(self.dropdownElement, 'active');
|
||||
}
|
||||
});
|
||||
|
||||
this.selectField.addEventListener('keydown', function (e) {
|
||||
if (e.keyCode === 13 || e.keyCode === 40) {
|
||||
jsOMS.preventAll(e);
|
||||
}
|
||||
|
||||
if (e.keyCode === 40) {
|
||||
// down-key
|
||||
self.selectOption(self.dataListBody.firstElementChild);
|
||||
jsOMS.preventAll(e);
|
||||
} else {
|
||||
// handle change delay
|
||||
self.inputTimeDelay({ id: self.id, delay: 300 }, self.changeCallback, self, e);
|
||||
}
|
||||
});
|
||||
|
||||
this.selectField.addEventListener('focusin', function (e) {
|
||||
jsOMS.addClass(self.dropdownElement, 'active');
|
||||
});
|
||||
|
||||
this.dropdownElement.addEventListener('keydown', function (e) {
|
||||
jsOMS.preventAll(e);
|
||||
|
||||
// todo: consider if it makes sense to have a none element always for phone users only to jump out?
|
||||
// todo: if not remote then the suggestion dropdown should filter itself based on best match
|
||||
|
||||
if (e.keyCode === 27 || e.keyCode === 46 || e.keyCode === 8) {
|
||||
// handle esc, del to go back to input field
|
||||
self.inputField.focus();
|
||||
self.clearDataListSelection(self);
|
||||
} else if (e.keyCode === 38) {
|
||||
// handle up-click
|
||||
if (document.activeElement.previousElementSibling !== null) {
|
||||
self.clearDataListSelection(self);
|
||||
self.selectOption(document.activeElement.previousElementSibling);
|
||||
}
|
||||
} else if (e.keyCode === 40) {
|
||||
// handle down-click
|
||||
if (document.activeElement.nextElementSibling !== null) {
|
||||
self.clearDataListSelection(self);
|
||||
self.selectOption(document.activeElement.nextElementSibling);
|
||||
}
|
||||
} else if (e.keyCode === 13 || e.keyCode === 9) {
|
||||
self.clearDataListSelection(self);
|
||||
self.addToResultList(self);
|
||||
}
|
||||
});
|
||||
|
||||
this.dropdownElement.addEventListener('focusout', function (e) {
|
||||
self.clearDataListSelection(self);
|
||||
jsOMS.removeClass(self.dropdownElement, 'active');
|
||||
});
|
||||
|
||||
this.dropdownElement.addEventListener('click', function (e) {
|
||||
if (document.activeElement.tagName.toLowerCase() !== 'tr') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.clearDataListSelection(self);
|
||||
self.addToResultList(self);
|
||||
jsOMS.removeClass(self.dropdownElement, 'active');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle remote data response result
|
||||
*
|
||||
* This method adds remote results to the dropdown list for selecting
|
||||
*
|
||||
* @param {object} self This reference
|
||||
* @param {object} data Response data
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
remoteCallback(self, data) {
|
||||
console.log(data);
|
||||
data = JSON.parse(data.response)[0];
|
||||
const dataLength = data.length;
|
||||
|
||||
console.table(data);
|
||||
|
||||
// if dropdown == true
|
||||
if (self.dropdownElement.getAttribute('data-active') === 'true') {
|
||||
while (self.dataListBody.firstChild) {
|
||||
self.dataListBody.removeChild(self.dataListBody.firstChild);
|
||||
}
|
||||
|
||||
for (let i = 0; i < dataLength; ++i) {
|
||||
// set readable value
|
||||
const newRow = self.dataTpl.content.cloneNode(true);
|
||||
let fields = newRow.querySelectorAll('[data-tpl-text]');
|
||||
let fieldLength = fields.length;
|
||||
|
||||
for (let j = 0; j < fieldLength; ++j) {
|
||||
fields[j].appendChild(
|
||||
document.createTextNode(
|
||||
jsOMS.getArray(fields[j].getAttribute('data-tpl-text'), data[i])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// set internal value
|
||||
fields = newRow.querySelectorAll('[data-tpl-value]');
|
||||
fieldLength = fields.length;
|
||||
|
||||
for (let j = 0; j < fieldLength; ++j) {
|
||||
fields[j].setAttribute(
|
||||
'data-value',
|
||||
jsOMS.getArray(fields[j].getAttribute('data-tpl-value'), data[i])
|
||||
);
|
||||
}
|
||||
|
||||
self.dataListBody.appendChild(newRow);
|
||||
self.dataListBody.lastElementChild.addEventListener('focusout', function (e) {
|
||||
if (e.relatedTarget === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sibling = e.relatedTarget.parentNode.firstElementChild;
|
||||
do {
|
||||
if (sibling === e.relatedTarget) {
|
||||
jsOMS.preventAll(e);
|
||||
return;
|
||||
}
|
||||
} while ((sibling = sibling.nextElementSibling) !== null);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for input field content change
|
||||
*
|
||||
* @param {object} self This reference
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
changeCallback(self) {
|
||||
// if remote data
|
||||
if (typeof self.src !== 'undefined' && self.src !== '') {
|
||||
const request = new Request(self.src);
|
||||
request.setSuccess(function (data) { self.remoteCallback(self, data); });
|
||||
request.send();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Select element in dropdown (only mark it as selected)
|
||||
*
|
||||
* @param {object} e Element to select in dropdown
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
selectOption(e) {
|
||||
e.focus();
|
||||
// todo: change to set style .active
|
||||
e.setAttribute('style', 'background: #f00');
|
||||
jsOMS.addClass(e, 'active');
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all selected/marked options in dropdown
|
||||
*
|
||||
* @param {object} self This reference
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
clearDataListSelection(self) {
|
||||
const list = self.dataListBody.getElementsByTagName('tr'),
|
||||
length = list.length;
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
// todo: remove the active class
|
||||
list[i].setAttribute('style', '');
|
||||
jsOMS.removeClass(list[i], 'active');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add selected dropdown elements to some final result list
|
||||
*
|
||||
* This can add the selected dropdown elements to a table, badge list etc. depending on the template structure.
|
||||
*
|
||||
* @param {object} self This reference
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
addToResultList(self) {
|
||||
if (self.inputField.getAttribute('data-autocomplete') === 'true') {
|
||||
self.inputField.value = document.activeElement.querySelectorAll('[data-tpl-value="' + self.inputField.getAttribute('data-value') + '"]')[0].getAttribute('data-value');
|
||||
}
|
||||
|
||||
if (self.tagElement.getAttribute('data-active') === 'true') {
|
||||
// todo: make badges removable
|
||||
const newTag = self.tagTpl.content.cloneNode(true);
|
||||
|
||||
// set internal value
|
||||
let fields = newTag.querySelectorAll('[data-tpl-value]');
|
||||
let fieldLength = fields.length;
|
||||
let uuid = '';
|
||||
let value = '';
|
||||
|
||||
for (let j = 0; j < fieldLength; ++j) {
|
||||
value = document.activeElement.querySelectorAll('[data-tpl-value="' + fields[j].getAttribute('data-tpl-value') + '"]')[0].getAttribute('data-value');
|
||||
fields[j].setAttribute('data-value', value);
|
||||
|
||||
uuid += value;
|
||||
}
|
||||
|
||||
// don't allow duplicate
|
||||
// todo: create a data-duplicat=true attribute to allow duplication and then have a count as part of the uuid (maybe row id)
|
||||
if (self.tagElement.querySelectorAll('[data-tpl-uuid="' + uuid + '"').length !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
newTag.firstElementChild.setAttribute('data-tpl-uuid', uuid);
|
||||
|
||||
// set readable text
|
||||
fields = newTag.querySelectorAll('[data-tpl-text]');
|
||||
fieldLength = fields.length;
|
||||
|
||||
for (let j = 0; j < fieldLength; ++j) {
|
||||
fields[j].appendChild(
|
||||
document.createTextNode(
|
||||
document.activeElement.querySelectorAll('[data-tpl-text="' + fields[j].getAttribute('data-tpl-text') + '"]')[0].innerText
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// allow limit
|
||||
if (self.tagElement.childElementCount >= self.tagElement.getAttribute('data-limit')
|
||||
&& self.tagElement.getAttribute('data-limit') != 0
|
||||
) {
|
||||
self.tagElement.removeChild(self.tagElement.firstElementChild);
|
||||
}
|
||||
|
||||
self.tagElement.appendChild(newTag);
|
||||
}
|
||||
|
||||
if (self.inputField.getAttribute('data-emptyAfter') === 'true') {
|
||||
self.inputField.value = '';
|
||||
}
|
||||
|
||||
self.inputField.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delay handler (e.g. delay after finishing typing)
|
||||
*
|
||||
* After waiting for a delay a callback can be triggered.
|
||||
*
|
||||
* @param {object} action Action type
|
||||
* @param {function} callback Callback to be triggered
|
||||
* @param {object} self This reference (passed to callback)
|
||||
* @param {object} data Data (passed to callback)
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
inputTimeDelay(action, callback, self, data) {
|
||||
if (AdvancedSelect.timerDelay[action.id]) {
|
||||
clearTimeout(AdvancedSelect.timerDelay[action.id]);
|
||||
delete AdvancedSelect.timerDelay[action.id]
|
||||
}
|
||||
|
||||
AdvancedSelect.timerDelay[action.id] = setTimeout(function () {
|
||||
delete AdvancedSelect.timerDelay[action.id];
|
||||
callback(self, data);
|
||||
}, action.delay);
|
||||
};
|
||||
};
|
||||
|
||||
AdvancedSelect.timerDelay = {};
|
||||
|
|
@ -382,7 +382,7 @@ export class Form {
|
|||
'#' + createForm + ' [data-tpl-value="' + fields[j].getAttribute('data-tpl-value') + '"], [data-form="' + createForm + '"][data-tpl-value="' + fields[j].getAttribute('data-tpl-value') + '"]')[0]
|
||||
.getAttribute('data-value');
|
||||
|
||||
// todo: we need to check what kind of tag the selector above returns in order to get the correct value. currently this only makes sense for input elements but for selection, checkboxes etc. this doesn't make sense there we need .innerHtml or [data-text=]
|
||||
// todo: we need to check what kind of tag the selector above returns in order to get the correct value. currently this only makes sense for input elements but for selection, checkboxes etc. this doesn't make sense there we need .innerHTML or [data-text=]
|
||||
|
||||
fields[j].setAttribute('data-value', value);
|
||||
|
||||
|
|
@ -407,7 +407,7 @@ export class Form {
|
|||
)
|
||||
);
|
||||
|
||||
// todo: we need to check what kind of tag the selector above returns in order to get the correct value. currently this only makes sense for input elements but for selection, checkboxes etc. this doesn't make sense there we need .innerHtml or [data-text=]
|
||||
// todo: we need to check what kind of tag the selector above returns in order to get the correct value. currently this only makes sense for input elements but for selection, checkboxes etc. this doesn't make sense there we need .innerHTML or [data-text=]
|
||||
}
|
||||
|
||||
subMain.appendChild(newEle);
|
||||
|
|
@ -566,9 +566,27 @@ export class Form {
|
|||
for (let i = 0; i < length; ++i) {
|
||||
for (let j = 0; j < selectorLength; ++j) {
|
||||
const matches = newEle[j].firstElementChild.querySelectorAll('[data-tpl-value="' + values[i].getAttribute('data-tpl-value') + '"');
|
||||
if (matches.length > 0) {
|
||||
|
||||
const matchLength = matches.length;
|
||||
for (let c = 0; c < matchLength; ++c) {
|
||||
// todo: handle multiple matches
|
||||
matches[0].value = values[i].value;
|
||||
// todo: urls remote src shouldn't start with http (it can but also the base path should be allowed or the current uri as basis... maybe define a data-tpl-value-srctype??? however this sounds stupid and might be too verbose or use http as identifier and use the fact that the request object uses internally the uri factory!!! sounds much smarter :))
|
||||
// todo: implement this for other cases as well or potentially pull it out because it is very similar!!!
|
||||
if (values[i].getAttribute('data-tpl-value').startsWith('http')
|
||||
|| values[i].getAttribute('data-tpl-value').startsWith('{')
|
||||
) {
|
||||
const request = new Request(values[i].getAttribute('data-tpl-value'));
|
||||
request.setResultCallback(200, function(xhr) {
|
||||
matches[c].value = xhr.response;
|
||||
// todo: the problem with this is that the response must only return the markdown or whatever is requested. It would be much nicer if it would also possible to define a path for the response in case a json object is returned which is very likely
|
||||
});
|
||||
|
||||
request.send();
|
||||
} else {
|
||||
matches[c].value = self.getValueFromDataSource(values[i]);
|
||||
matches[c].innerHTML = values[i].innerText;
|
||||
}
|
||||
// todo handle remote data (e.g. value ist coming from backend. do special check for http)
|
||||
}
|
||||
// todo: handle different input types
|
||||
}
|
||||
|
|
@ -579,10 +597,14 @@ export class Form {
|
|||
for (let i = 0; i < length; ++i) {
|
||||
for (let j = 0; j < selectorLength; ++j) {
|
||||
const matches = newEle[j].firstElementChild.querySelectorAll('[data-tpl-text="' + text[i].getAttribute('data-tpl-text') + '"');
|
||||
if (matches.length > 0) {
|
||||
|
||||
const matchLength = matches.length;
|
||||
for (let c = 0; c < matchLength; ++c) {
|
||||
// todo: handle multiple matches
|
||||
matches[0].value = text[i].innerText;
|
||||
// todo: handle different input types
|
||||
matches[c].value = self.getTextFromDataSource(text[i]);
|
||||
matches[c].innerHTML = text[i].innerHTML; // example for article instead of input field without value
|
||||
// todo: handle different input types e.g. Article requires innerHTML instead of value
|
||||
// todo handle remote data (e.g. value ist coming from backend. do special check for http)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -708,9 +730,13 @@ export class Form {
|
|||
for (let i = 0; i < length; ++i) {
|
||||
for (let j = 0; j < selectorLength; ++j) {
|
||||
const matches = parentsContent[j].querySelectorAll('[data-tpl-value="' + values[i].getAttribute('data-tpl-value') + '"');
|
||||
if (matches.length > 0) {
|
||||
|
||||
|
||||
const matchLength = matches.length;
|
||||
for (let c = 0; c < matchLength; ++c) {
|
||||
// todo: handle multiple matches
|
||||
matches[0].value = values[i].value;
|
||||
matches[c].value = self.getValueFromDataSource(values[i]);
|
||||
// todo handle remote data (e.g. value ist coming from backend. do special check for http)
|
||||
}
|
||||
// todo: handle different input types
|
||||
}
|
||||
|
|
@ -721,10 +747,13 @@ export class Form {
|
|||
for (let i = 0; i < length; ++i) {
|
||||
for (let j = 0; j < selectorLength; ++j) {
|
||||
const matches = parentsContent[j].querySelectorAll('[data-tpl-text="' + text[i].getAttribute('data-tpl-text') + '"');
|
||||
if (matches.length > 0) {
|
||||
|
||||
const matchLength = matches.length;
|
||||
for (let c = 0; c < matchLength; ++c) {
|
||||
// todo: handle multiple matches
|
||||
matches[0].innerText = text[i].value;
|
||||
matches[c].innerText = self.getTextFromDataSource(text[i]);
|
||||
// todo: handle different input types
|
||||
// todo handle remote data (e.g. value ist coming from backend. do special check for http)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -803,6 +832,8 @@ export class Form {
|
|||
*/
|
||||
bindUpdatableExternal(update, id)
|
||||
{
|
||||
const self = this;
|
||||
|
||||
update.addEventListener('click', function () {
|
||||
const formElement = document.getElementById(id);
|
||||
const parent = this.closest(formElement.getAttribute('data-ui-element'));
|
||||
|
|
@ -821,16 +852,29 @@ export class Form {
|
|||
length = values.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
// todo: handle multiple matches
|
||||
document.getElementById(formId).querySelectorAll('[data-tpl-value="' + values[i].getAttribute('data-tpl-value') + '"')[0].value = values[i].value;
|
||||
const matches = document.getElementById(formId).querySelectorAll('[data-tpl-value="' + values[i].getAttribute('data-tpl-value') + '"');
|
||||
|
||||
const matchLength = matches.length;
|
||||
for (let c = 0; c < matchLength; ++c) {
|
||||
matches[c].value = self.getValueFromDataSource(values[i]);
|
||||
}
|
||||
// todo: handle different input types
|
||||
// todo handle remote data (e.g. value ist coming from backend. do special check for http)
|
||||
}
|
||||
|
||||
// insert row text data into form
|
||||
length = text.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
// todo: handle multiple matches
|
||||
document.getElementById(formId).querySelectorAll('[data-tpl-text="' + text[i].getAttribute('data-tpl-text') + '"')[0].value = text[i].innerText;
|
||||
const matches = document.getElementById(formId).querySelectorAll('[data-tpl-text="' + text[i].getAttribute('data-tpl-text') + '"');
|
||||
|
||||
// consider pulling this out because it exists like 3x2 = 6 times in a similar way or at least 3 times very similarly
|
||||
const matchLength = matches.length;
|
||||
for (let c = 0; c < matchLength; ++c) {
|
||||
matches[c].value = self.getTextFromDataSource(text[i]);
|
||||
}
|
||||
// todo: handle different input types
|
||||
// todo handle remote data (e.g. value ist coming from backend. do special check for http)
|
||||
}
|
||||
|
||||
// todo: replace add button with save button and add cancel button
|
||||
|
|
@ -875,4 +919,24 @@ export class Form {
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
setValueOfElement(src, dest)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
setTextOfElement(src, dest)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
getValueFromDataSource(src)
|
||||
{
|
||||
return src.value;
|
||||
};
|
||||
|
||||
getTextFromDataSource(src)
|
||||
{
|
||||
return src.value;
|
||||
};
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
34
Utils/Parser/license.txt
Normal file
34
Utils/Parser/license.txt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
Showdown Copyright (c) 2007, John Fraser
|
||||
<http://www.attacklab.net/>
|
||||
All rights reserved.
|
||||
|
||||
Original Markdown copyright (c) 2004, John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name "Markdown" nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright owner
|
||||
or contributors be liable for any direct, indirect, incidental, special,
|
||||
exemplary, or consequential damages (including, but not limited to,
|
||||
procurement of substitute goods or services; loss of use, data, or
|
||||
profits; or business interruption) however caused and on any theory of
|
||||
liability, whether in contract, strict liability, or tort (including
|
||||
negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
Loading…
Reference in New Issue
Block a user