آموزش 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); })
ارسال نظر