آموزش clone یا نمونه سازی از تگ های html در جاوا اسکریپت

clone از html با جاوا اسکریپت و کتابخانه jquery cloner ، با کمک این کتابخانه می توانیم خیلی راحت از تگ های html نمونه بگیریم .
این پروژه را در 2 سطح انجام می دهیم :
- نمونه عادی
- نمونه تو در تو
خروجی برنامه
در نمونه تو در تو از تقویم فارسی ، select2 و نمونه در نمونه استفاده می کنیم .
ویژگی که این پروژه دارد این است که در هر تغییر input های فرم از جمله select در hidden داده ها به صورت آرایه ذخیره می شود .
جزئیات dataset ها
- data-seperator : زمانی که بخواهیم اعداد را به صورت 3 رقم 3 رقم جدا کنیم از این ویژگی استفاده می کنیم
- data-value : این ویژگی مورد شماره 1 مرتبط است یا موارد مشابه در صورتی که بخواهیم مقدار 1,234 را به صورت 1234 ذخیره کنیم به کار مان می آید .
- data-optional : در صورتی که بخواهیم بگوییم این فیلد در لیست مان اختیاری است این dataset به کار مان می آید . دلیل آن این است که هر مقدار که در hidden ذخیره باشد و دارای مقدار null باشد فرم ناقص تلقی می شود با این ویژگی به جای null مقدار “” ذخیره می کنیم .
- data-empty : این ویژگی فقط برای فیلد هایی که کلاس data-field ذخیره می شود همان hidden که داده ها را به صورت آرایه ذخیره می کند و در صورتی که در آرایه فیلدی را کاربر کامل نکرده باشد این ویژگی ظاهر می شود .
نمونه عادی
<!DOCTYPE html>
<html lang="fa">
<head>
<meta charset="UTF-8">
<title>Rapidcode.iR - سورس کد</title>
<link rel="stylesheet" href="static/css/lib/persian-datepicker-custom.min.css">
<link rel="stylesheet" href="static/css/lib/persian-datepicker.min.css">
<link rel="stylesheet" href="static/css/lib/select2.min.css">
<link rel="stylesheet" href="static/css/main.css">
</head>
<body>
<div class="container">
<a id="introduce" href="https://rapidcode.ir" target="_blank">رپید کد • کتابخانه مجازی برنامه نویسان</a>
<h1>تکرار شونده عادی - clone simple elements</h1>
<div class="form-group clonable-block-parent specification">
<label id="specification-label" class="control-label">مشخصات</label>
<br>
<div class="custom-control custom-checkbox">
<input type="checkbox" data-parent="specification" class="custom-control-input activatation" name="specification-activate" id="specification-activate">
<label class="custom-control-label" for="specification-activate">فعال / غیرفعال</label>
</div>
<hr>
<table>
<tbody class="clonable-block clone_specification" id="clone_specification" data-parent="specification">
<tr class="clonable" data-funcs='["loopThrowIndexLabelCloner"]'>
<td>
<label for="specification_1" class="clonable-increment-for highlight-orange">مشخصه <span class="clonable-html-number">1</span></label>
<select id="specification_1" data-name="term_id_specification" data-parent="clone_specification" class="form-control input-json select2 select2ajax clipboardIT clonable-increment-id" data-taxonomy="specification" data-clipboard-id="specification_name"></select>
<button type="button" class="clonable-button-close btn btn-danger">حذف</button>
<button type="button" class="clonable-button-add btn btn-info">افزودن</button>
</td>
<td>
<input type="text" data-name="term_val_specification" class="input-json drag-top specification_value form-control clonable-increment-id clonable-increment-name" id="specification_value_1" placeholder="مقدار مشخصه">
</td>
</tr>
</tbody>
</table>
<input type="hidden" id="specification-terms" data-empty="true" class="data-field" name="specification-terms" value="" data-label="specification-label">
</div>
</div>
<script src="static/js/lib/jquery.min.js"></script>
<script src="static/js/lib/jquery.cloner.min.js"></script>
<script src="static/js/lib/persian-date.min.js"></script>
<script src="static/js/lib/persian-datepicker.min.js"></script>
<script src="static/js/lib/select2.min.js"></script>
<script src="static/js/lib/select2fa.min.js"></script>
<script src="static/js/app.js"></script>
</body>
</html>
نمونه تو در تو
<!DOCTYPE html>
<html lang="fa">
<head>
<meta charset="UTF-8">
<title>Rapidcode.iR - سورس کد</title>
<link rel="stylesheet" href="static/css/lib/persian-datepicker-custom.min.css">
<link rel="stylesheet" href="static/css/lib/persian-datepicker.min.css">
<link rel="stylesheet" href="static/css/lib/select2.min.css">
<link rel="stylesheet" href="static/css/main.css">
</head>
<body>
<div class="container">
<a id="introduce" href="https://rapidcode.ir" target="_blank">رپید کد • کتابخانه مجازی برنامه نویسان</a>
<h1>تکرار شونده تودرتو - clone nested elements</h1>
<div class="form-group clonable-block-parent variation">
<label id="variation-label" class="control-label">ویژگی ها</label>
<div class="custom-control custom-checkbox">
<input type="checkbox" data-parent="variation" class="custom-control-input activatation" name="variation-activate" id="variation-activate">
<label class="custom-control-label" for="variation-activate">فعال / غیرفعال</label>
</div>
<hr>
<table>
<tbody class="clonable-block clone_variation" id="clone_variation" data-parent="variation">
<tr class="clonable depth-1-row" data-funcs='["loopThrowIndexLabelCloner"]'>
<td>
<label class="highlight-orange">ویژگی <span data-parent="depth-1-row" class="clonable-html-number">1</span></label><br><br>
<table>
<tbody id="depth-2-1" class="clonable-block depth-2-1 depth-2" data-parent="variation">
<tr class="clonable depth-2-row" data-funcs='["loopThrowIndexLabelCloner"]'>
<td><label for="variation_1" class="clonable-increment-for">نام ویژگی <span data-parent="depth-2-row" class="clonable-html-number">1</span></label>
<select id="variation_1" data-name="term_id_variation" data-parent="depth-2-1" class="form-control input-json select2 select2ajax clipboardIT clonable-increment-id" data-taxonomy="variation" data-clipboard-id="variation_name"></select>
<button type="button" class="clonable-button-close btn btn-danger">حذف</button>
<button type="button" class="clonable-button-add btn btn-info">افزودن</button>
</td>
<td>
<input id="variation_value_1" data-name="term_val_variation" type="text" class="input-json drag-top form-control clonable-increment-id" placeholder="محتوای ویژگی">
</td>
</tr>
</tbody>
</table>
<table id="depth-2-2" class="depth-2 regular">
<tr>
<td><label for="guarantee_1" class="clonable-increment-for">گارانتی</label>
<select id="guarantee_1" data-optional="true" data-name="term_id_guarantee" data-parent="clone_variation" class="form-control input-json select2 select2ajax clipboardIT clonable-increment-id" data-taxonomy="guarantee" data-clipboard-id="variation_guarantee"></select><br><br>
</td>
<td><label for="guarantee_price_1" class="clonable-increment-for">هزینه گارانتی ( تومان )</label><br><br>
<input id="guarantee_price_1" data-optional="true" data-seperator="true" data-name="term_val_guarantee" type="text" class="input-json drag-top form-control clonable-increment-id">
</td>
</tr>
<tr>
<td><label for="enitity_1" class="clonable-increment-for">موجودی</label>
<input id="enitity_1" data-seperator="true" data-name="variation_entity" type="text" data-parent="clone_variation" class="form-control input-json clonable-increment-id"><br>
</td>
</tr>
<tr>
<td> <label for="price_1" class="clonable-increment-for">قیمت عادی ( تومان )</label>
<input id="price_1" data-seperator="true" data-name="variation_price" type="text" data-parent="clone_variation" class="form-control input-json clonable-increment-id"><br>
</td>
<td> <label for="price_sale_1" class="clonable-increment-for">قیمت حراج ( تومان )</label>
<input id="price_sale_1" data-optional="true" data-seperator="true" data-name="variation_price_sale" type="text" data-parent="clone_variation" class="form-control input-json clonable-increment-id"><br>
</td>
<td><label for="price_sale_expire_date_1" class="clonable-increment-for">تاریخ انقضاء حراج</label>
<input type="text" data-optional="true" data-name="variation_price_sale_expire" id="price_sale_expire_date_1" data-parent="clone_variation" class="form-control input-json date-picker-shamsi clonable-increment-id clipboardIT" data-clipboard-id="variation_expire_date">
</td>
</tr>
</table>
<button type="button" class="clonable-button-close btn btn-danger">حذف</button>
<button type="button" class="clonable-button-add btn btn-info">افزودن</button>
</td>
</tr>
</tbody>
</table>
<input type="hidden" id="variation-terms" data-empty="true" class="data-field" name="variation-terms" value="" data-label="variation-label">
</div>
</div>
<script src="static/js/lib/jquery.min.js"></script>
<script src="static/js/lib/jquery.cloner.min.js"></script>
<script src="static/js/lib/persian-date.min.js"></script>
<script src="static/js/lib/persian-datepicker.min.js"></script>
<script src="static/js/lib/select2.min.js"></script>
<script src="static/js/lib/select2fa.min.js"></script>
<script src="static/js/app.js"></script>
</body>
</html>
فایل main.css
.container {
margin: 0 auto;
width: 80%;
text-align: center;
direction: rtl;
}
#introduce {
display: block;
width: 100%;
font-size: 35px;
font-weight: bold;
color: white;
padding-bottom: 5px;
background-color: #4CAF50;
text-decoration: none;
margin-bottom: 15px;
}
/* Page Style */
.select2{
width: 200px !important;
}
اسکریپت app.js
$(document).ready(function(e){
window.generatePersianDatepickerFeature = function (element, options = {}) {
const res = element.persianDatepicker(options);
return res;
}
window.generateSelect2Feature = function (element, options = {}) {
const res = element.select2(options);
return res;
}
window.destroySelect2Feature = function (element) {
element.select2("destroy");
}
window.showSelect2AjaxResult = function (data, li) {
if (data.loading || !data.id) {
return data.text;
}
var container = $(`<div class="result-select2-ajax">${data.name}</div>`);
return container;
}
window.showSelection2AjaxResult = function (data) {
return data.name || "انتخاب کنید";
}
window.onDeleteRow = function (e) {
const currentElement = $(e.target);
const clone = currentElement.parents('.clonable').first();
const cloneBlock = clone.parent('.clonable-block').first();
const firstClone = cloneBlock.find('.clonable').first();
setTimeout(function (clone) {
if (clone.length) {
triggerCloneInput(clone);
}
}, 100, firstClone)
}
window.select2Clear = function (e) {
const targetElement = $(e.target);
const targetElementVal = targetElement.val();
if (!targetElementVal) {
targetElement.empty();
}
}
window.loopThrowClonableLocal = function (clone, index, callback = null) {
const parent = clone.parent();
const cloneClassList = clone.attr('class');
const selector = generateCssClass(cloneClassList, ['clonable-source', 'clonable-clone']);
const cloneList = parent.find(selector);
if (callback) {
return callback({
clone: clone,
index: index,
cloneList: cloneList
});
}
}
window.callListedFunctions = function (args) {
const funcList = JSON.parse(args.clone.attr('data-funcs'));
for (const ch of funcList) {
args.cloneList.each(window[ch]);
}
}
window.generateCssClass = function (classListStr, except = []) {
let cssSelector = classListStr;
for (const ex of except) {
cssSelector = cssSelector.replace(ex, "");
}
cssSelector = cssSelector.replace(/\s{2,}/gi, " ").replace(" ", ".").replace(/\.\s|\.$/gi, "").trim();
cssSelector = "." + cssSelector;
return cssSelector;
}
window.loopThrowIndexLabelCloner = function (index, element) {
element = $(element);
const theIndex = index + 1;
let depthChecker = false;
if (element.attr("class").search("depth-") != -1) {
depthChecker = true;
}
const elements = element.find('.clonable-html-number');
for (const elementChild of elements) {
if (depthChecker) {
if (element.attr("class").search($(elementChild).attr('data-parent')) != -1) {
$(elementChild).html(theIndex);
}
continue;
}
$(elementChild).html(theIndex);
}
}
window.getUniqueIdDOM = function(DOM , id , seperator = "_t_"){
let theID = id ? id : DOM.attr('id');
theID = theID.split(seperator);
theID = theID[0];
const finallID = theID + seperator + Date.now().toString().slice(-4);
DOM.attr('id' , finallID);
return finallID;
}
window.generateUniqueIdDom = function(clone , except = []){
const theExcept = except.join(",");
const inputs = clone.find(`.input-json:not(${theExcept})`);
for(let input of inputs){
input = $(input);
getUniqueIdDOM(input);
}
}
window.select2ClonerFixer = function (clone, index = 0) {
let parentElement = clone.parent();
const select2Selector = `.select2[data-parent=${parentElement.attr('id')}]`;
let select2 = clone.find(select2Selector);
if (!select2.length) return;
const cloneInDepth = clone.find('.clonable');
if (cloneInDepth.length) select2ClonerFixer(cloneInDepth.eq(0));
let select2PreviousElement = select2.prev();
let select2NextElement = select2.next();
const select2ID = select2.attr('data-clipboard-id');
const classList = select2.attr('class');
const id = select2PreviousElement.attr('for');
select2.remove();
select2NextElement.remove();
const DOM = $(window[select2ID]);
DOM.attr('class', classList);
getUniqueIdDOM(DOM , id);
select2PreviousElement.after(DOM)
select2 = clone.find(select2Selector);
generateSelect2Feature(select2, select2AjaxOptions)
}
window.persianDatePickerClonerFixer = function (clone, index) {
const datePickerDOM = clone.find('input.date-picker-shamsi');
if (!datePickerDOM.length) return;
const datePickerDOMPrevious = datePickerDOM.prev();
const datePickerID = datePickerDOM.attr('id');
const DOM = $(window[datePickerDOM.attr('data-clipboard-id')]);
DOM.attr('id', datePickerID);
datePickerDOM.remove();
datePickerDOMPrevious.after(DOM);
generatePersianDatepickerFeature(DOM, shamsiDatePickerOptions);
}
window.cloneActivation = function (e) {
const currentElement = $(e.target);
const checked = currentElement.prop('checked');
const parent = $('.' + currentElement.attr('data-parent'));
const dataField = parent.find('.data-field');
if (checked) {
loopThrowInputsJsonClass('.input-json', parent, emptyInputs);
loopThrowInputsJsonClass('.input-json', parent, disableInputs);
dataField.val('');
dataField.removeAttr("data-empty");
} else {
loopThrowInputsJsonClass('.input-json', parent, enableInputs);
dataField.attr("data-empty", "true");
}
parent.toggleClass('cover-disable');
}
window.onInputNumberSeperator = function (e) {
const thisElement = $(e.target);
const value = thisElement.val().replace(/,/gi, "").replace(/\D/gi, "");
if (isNaN(Number(value))) return;
let seperatedValue = new Intl.NumberFormat('en-US', { style: "decimal" }).format(value);
if (seperatedValue == 0) seperatedValue = '';
thisElement.val(seperatedValue);
thisElement.attr('data-value', value);
}
window.generateDataInputsClonable = function (args) {
const dataField = args.parent.find('.data-field');
for (let input of args.inputs) {
input = $(input);
const targetEvent = getEventTarget(input);
if (!targetEvent) continue;
input.off(targetEvent, window.valueListenerClonable);
input.on(targetEvent, null, {
dataField: dataField,
inputs: args.inputs
}, window.valueListenerClonable);
}
}
window.loopThrowClonableGlobal = function (clone, index, callback) {
const cloneParent = clone.parent();
const cloneParentParent = $('.' + cloneParent.attr('data-parent'));
const cloneList = cloneParentParent.find(".clonable");
if (callback) {
return callback({
clone: clone,
index: index,
cloneParentParent: cloneParentParent,
cloneList: cloneList
})
}
}
window.loopThrowInputsClonable = function (args) {
const inputs = args.cloneParentParent.find('.input-json');
return {
parent: args.cloneParentParent,
inputs: inputs
};
}
window.emptyInputs = function (input) {
const eventTarget = getEventTarget(input);
input.val(null).trigger(eventTarget);
}
window.disableInputs = function (input) {
input.attr('disabled', 'disabled');
}
window.enableInputs = function (input) {
input.removeAttr('disabled');
}
window.valueListenerClonable = function (e) {
const dataField = $(e.data.dataField);
const inputs = $(e.data.inputs);
let jsonData = [];
let key = '';
let val = '';
const inputListID = [];
function checkDuplicateInput(list , element){
if (list.includes(element) || $('#' + element).length == 0) {
return true;
}
return false;
}
for (let input of inputs) {
const theID = $(input).attr('id');
if(checkDuplicateInput(inputListID , theID)) continue;
input = $(input);
const row = loopToGetParent(input, 'data-funcs');
const jsonData2 = [];
if (row.length) {
const rowInputs = row.find('.input-json');
for (let rowInput of rowInputs) {
const theID = $(rowInput).attr('id');
if(checkDuplicateInput(inputListID , theID)) continue;
rowInput = $(rowInput);
inputListID.push(rowInput.attr('id'));
key = rowInput.attr('data-name');
if (rowInput.attr('data-value') && rowInput.val())
val = rowInput.attr('data-value');
else
val = rowInput.val() ? rowInput.val() : null;
if (rowInput.attr('data-optional') && val === null)
val = '';
const objMap = {}
objMap[key] = val;
objMap['id'] = rowInput.attr('id');
jsonData2.push(objMap);
}
jsonData.push(jsonData2)
}
}
jsonData = JSON.stringify(jsonData);
console.log(jsonData);
dataField.val(jsonData)
makeItDataEmpty(dataField)
}
window.loopToGetParent = function (element, dataAttr) {
let parent = null;
let counter = 0
while (true) {
if (parent && (parent.attr(dataAttr) || counter == 15)) break;
parent = !parent ? element.parent() : parent.parent();
counter++;
}
if (parent && parent.parent().hasClass('depth-2')) return loopToGetParent(parent, dataAttr)
return parent;
}
window.makeItDataEmpty = function (element) {
const val = JSON.parse(element.val());
propertyList.DOM = element;
loopThrowObjects(val, indexerObjects);
propertyListReset([
"DOM",
"loopBreak"
]);
}
window.loopThrowObjects = function (objectList, callback) {
if (typeof objectList === "object" && objectList != null) {
const keys = Object.keys(objectList);
for (const key of keys) {
if (propertyList.loopBreak) return;
const mElement = objectList[key]
callback(mElement);
}
}
}
window.indexerObjects = function (element) {
if (typeof element === "object" && element != null) {
loopThrowObjects(element, indexerObjects);
} else {
if (element === null) {
if (propertyList.DOM) {
propertyList.DOM.attr('data-empty', 'true');
}
propertyList.loopBreak = true;
} else if (element) {
propertyList.DOM.removeAttr('data-empty');
}
}
}
window.getEventTarget = function (element) {
const tagName = element.prop('tagName').toLowerCase();
const targetEvent = eventUpdateNames[tagName];
return targetEvent;
}
window.propertyListReset = function (keys) {
for (const key of keys) {
window.propertyList[key] = null;
}
}
window.triggerCloneInput = function (element) {
const input = element.find('.input-json').last();
if (!input.length) return;
const targetEvent = getEventTarget(input);
input.trigger(targetEvent);
}
window.triggerClone = function (element) {
const index = 0;
generateDataInputsClonable(loopThrowClonableGlobal(element, index, loopThrowInputsClonable));
triggerCloneInput(element);
}
window.loopThrowInputsJsonClass = function (selector, parent, callback) {
const inputs = parent ? parent.find(selector) : $(selector);
for (let input of inputs) {
input = $(input);
callback(input);
}
}
window.shamsiDatePickerOptions = {
format: 'X',
timePicker: {
enabled: true
},
initialValue: false,
onSelect: function () {
const thisElement = $(this.model.inputElement);
thisElement.trigger('input');
}
}
window.select2AjaxOptions = {
ajax: {
url: "https://api.rapidcode.ir/rest/index.php/products",
dataType: 'json',
data: function (params) {
const taxonomy = $(this).attr('data-taxonomy');
const query = {
q: params.term,
taxonomy: taxonomy
}
return query;
},
processResults: function (data) {
return {
results: data,
};
},
cache: true
},
placeholder: 'انتخاب کنید',
allowClear: true,
minimumInputLength: 1,
language: "fa",
templateResult: function (data) { },
templateSelection: function (data) { }
};
window.jqueryClonerOptions = {
clonableContainer: '.clonable-block',
clonable: '.clonable',
addButton: '.clonable-button-add',
closeButton: '.clonable-button-close',
focusableElement: ':input:visible:enabled:first',
clearValueOnClone: true,
removeNestedClonablesOnClone: true,
limitCloneNumbers: true,
debug: false,
cloneName: 'clonable-clone',
sourceName: 'clonable-source',
clonableCloneNumberDecrement: 'clonable-clone-number-decrement',
incrementName: 'clonable-increment',
decrementName: 'clonable-decrement',
beforeToggle: function (clone, index, self) { },
afterToggle: function (clone, index, self) { },
onDeleteRow: onDeleteRow
}
window.propertyList = {
loopBreak: null,
DOM: null
}
window.eventUpdateNames = {
input: 'input',
select: 'change'
}
// fix clone select2
$('.clipboardIT').each(function (index, element) {
element = $(element);
window[element.attr('data-clipboard-id')] = element.get(0).outerHTML
})
// generate persian date picker
generatePersianDatepickerFeature($('input.date-picker-shamsi'), shamsiDatePickerOptions)
// generate active clone
$('.activatation').on('change', cloneActivation)
// generate active clone
$('.clonable-button-close').on('click', onDeleteRow)
// generate number seperator
$('input[data-seperator]').on("input", onInputNumberSeperator)
// enable Select2AJAX options
const select2AjaxOptionLocal = select2AjaxOptions;
select2AjaxOptionLocal.templateResult = showSelect2AjaxResult;
select2AjaxOptionLocal.templateSelection = showSelection2AjaxResult;
// enable Select2AJAX Specification
generateSelect2Feature($('.specification .select2.select2ajax'), select2AjaxOptionLocal);
// enable Select2AJAX Variation
generateSelect2Feature($('.variation .select2.select2ajax'), select2AjaxOptionLocal);
// set specification clone options
const cloneOptions = jqueryClonerOptions;
cloneOptions.afterToggle = function (clone, index) {
loopThrowClonableLocal(clone, index, callListedFunctions);
generateUniqueIdDom(clone , ['.select2']);
select2ClonerFixer(clone, index);
persianDatePickerClonerFixer(clone, index);
generateDataInputsClonable(loopThrowClonableGlobal(clone, index, loopThrowInputsClonable));
triggerClone(clone);
};
// enable specification clone
$('#clone_specification').cloner(cloneOptions).on('change', select2Clear);
// trigger specification first clone
triggerClone($('#clone_specification .clonable').first());
// enable variation clone
$('#clone_variation').cloner(cloneOptions).on('change', '.select2', select2Clear);
// trigger variation first clone
triggerClone($('#clone_variation .clonable').first());
// enable variation clone depth 2
$('.variation #depth-2-1').cloner(cloneOptions);
})
ارسال نظر