From 2a31047be7e08b7640653251f4a05895f0431870 Mon Sep 17 00:00:00 2001 From: Val <35149642+vkservicesllc@users.noreply.github.com> Date: Tue, 16 Apr 2019 18:21:13 -0400 Subject: [PATCH] update 1904161821 --- img/pix/desktop.ini | 2 - projects/google-apps/export.js | 1390 ++++++++++++++++++++++++++++++++ 2 files changed, 1390 insertions(+), 2 deletions(-) delete mode 100644 img/pix/desktop.ini create mode 100644 projects/google-apps/export.js diff --git a/img/pix/desktop.ini b/img/pix/desktop.ini deleted file mode 100644 index 7769b8b..0000000 --- a/img/pix/desktop.ini +++ /dev/null @@ -1,2 +0,0 @@ -[LocalizedFileNames] -signature.png=@signature.png,0 diff --git a/projects/google-apps/export.js b/projects/google-apps/export.js new file mode 100644 index 0000000..62c73fa --- /dev/null +++ b/projects/google-apps/export.js @@ -0,0 +1,1390 @@ +/** + * Updated: 2019-04-16 + */ + + +/** Form Entities **/ + +function Title_(value, row, column, height, width, horizontalAlignment, omitIndent) { + /* + * arguments: [value[, row[, column[, height[, width[, horizontalAlignment[, omitIndent]]]]]]] + * + * value -> string + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * horizontalAlignment -> selection op string options "left", "right" and "center" to override existing setting + * omitIndent -> boolean, if true indent will not be applied + * + */ + + return new RangeDescription(value, row, column, { + height: (height) ? height : undefined, + width: (width) ? width : undefined, + font: { family: 'Montserrat', size: 12, color: '#434343', weight: 'bold', line: 'underline' }, + alignment: (horizontalAlignment) ? { horizontal: horizontalAlignment } : undefined, + indent: (omitIndent) ? undefined : { left: 2, right: 2 }, + }); + +} + +function HeaderMain_(value, row, column, height, width, horizontalAlignment, omitIndent) { + /* + * arguments: [value[, row[, column[, height[, width[, horizontalAlignment[, omitIndent]]]]]]] + * + * value -> string + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * horizontalAlignment -> selection op string options "left", "right" and "center" to override existing setting + * omitIndent -> boolean, if true indent will not be applied + * + */ + + return new RangeDescription(value, row, column, { + height: (height) ? height : undefined, + width: (width) ? width : undefined, + font: { family: 'Roboto', size: 11, color: '#cccccc' }, + background: 'black', + border: { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + }, + alignment: (horizontalAlignment) ? { horizontal: horizontalAlignment } : undefined, + indent: (omitIndent) ? undefined : { left: 2, right: 2 }, + }); + +} + +function Header_(value, row, column, height, width, horizontalAlignment, omitIndent) { + /* + * arguments: [value[, row[, column[, height[, width[, horizontalAlignment[, omitIndent]]]]]]] + * + * value -> string + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * horizontalAlignment -> selection op string options "left", "right" and "center" to override existing setting + * omitIndent -> boolean, if true indent will not be applied + * + */ + + return new RangeDescription(value, row, column, { + height: (height) ? height : undefined, + width: (width) ? width : undefined, + font: { family: 'Roboto', size: 10, color: '#434343', style: 'italic' }, + border: { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + }, + alignment: (horizontalAlignment) ? { horizontal: horizontalAlignment } : undefined, + indent: (omitIndent) ? undefined : { left: 2, right: 2 }, + }); + +} + +function Label_(value, required, row, column, height, width, omitIndent, applyBorder) { + /* + * arguments: value[, required[, row[, column[, height[, width[, omitIndent[, applyBorder]]]]]]] + * + * value -> string + * required -> boolean, determines if label points at required field + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * omitIndent -> boolean, if true indent will not be applied + * applyBorder -> boolean, if true border will be applied + * + */ + + if (value && typeof value == 'string') { + + var fontColor = '#666666'; + if (required) { + value += ' *'; + fontColor = '#134f5c'; + } + + return new RangeDescription(value, row, column, { + height: (height) ? height : undefined, + width: (width) ? width : undefined, + wrapStrategy: (!height && !width && !applyBorder) ? SpreadsheetApp.WrapStrategy.OVERFLOW : undefined, + alignment: { horizontal: 'right' }, + font: { family: 'Roboto', color: fontColor }, + border: (applyBorder) ? { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + } : undefined, + indent: (omitIndent) ? undefined : { right: 2 }, + }); + + } + +} + +function Cell_(value, row, column, height, width, numberFormat, horizontalAlignment, omitIndent) { + /* + * arguments: [value[, row[, column[, height[, width[, numberFormat[, horizontalAlignment[, omitIndent]]]]]]]] + * + * value -> any type + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * numberFormat -> string reprsenting format pattern + * horizontalAlignment -> selection op string options "left", "right" and "center" to override existing setting + * omitIndent -> boolean, if true indent will not be applied + * + */ + + return new RangeDescription(value, row, column, { + height: (height) ? height : undefined, + width: (width) ? width : undefined, + numberFormat: (numberFormat) ? numberFormat : undefined, + font: { family: 'Poppins', size: 11 }, + border: { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + }, + alignment: (horizontalAlignment) ? { horizontal: horizontalAlignment } : undefined, + indent: (omitIndent) ? undefined : { left: 2, right: 2 }, + }); + +} + +function InputRequired_(value, row, column, height, width, validation, numberFormat, horizontalAlignment, unprotected) { + /* + * arguments: [value [,row[, column[, height[, width[, validation[, numberFormat[, horizontalAlignment[, unprotected]]]]]]]]] + * + * value -> any type + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * validation -> data validation object + * numberFormat -> string reprsenting format pattern + * horizontalAlignment -> selection op string options "left", "right" and "center" to override existing setting + * unprotected -> boolean, if false the range will be protected along with protected sheet + * + */ + + if (unprotected == undefined) unprotected = true; + + return new RangeDescription(value, row, column, { + tag: 'input', + height: (height) ? height : undefined, + width: (width) ? width : undefined, + numberFormat: (numberFormat) ? numberFormat : undefined, + font: { family: 'Poppins', size: 11, weight: 'bold' }, + background: '#c9daf8', + border: { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + }, + alignment: (horizontalAlignment) ? { horizontal: horizontalAlignment } : undefined, + validation: (validation) ? validation : undefined, + unprotected: unprotected, + }); + +} + +function InputOptional_(value, row, column, height, width, validation, numberFormat, horizontalAlignment, unprotected) { + /* + * arguments: [value [,row[, column[, height[, width[, validation[, numberFormat[, horizontalAlignment[, unprotected]]]]]]]]] + * + * value -> any type + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * validation -> data validation object + * numberFormat -> string reprsenting format pattern + * horizontalAlignment -> selection op string options "left", "right" and "center" to override existing setting + * unprotected -> boolean, if false the range will be protected along with protected sheet + * + */ + + if (unprotected == undefined) unprotected = true; + + return new RangeDescription(value, row, column, { + tag: 'input', + height: (height) ? height : undefined, + width: (width) ? width : undefined, + numberFormat: (numberFormat) ? numberFormat : undefined, + font: { family: 'Poppins', size: 11, weight: 'bold' }, + background: '#d0e0e3', + border: { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + }, + alignment: (horizontalAlignment) ? { horizontal: horizontalAlignment } : undefined, + validation: (validation) ? validation : undefined, + unprotected: unprotected, + }); + +} + +function InputAction_(value, row, column, height, width, validation) { + /* + * arguments: [value [,row[, column[, height[, width[, validation]]]]]] + * + * value -> any type + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * validation -> data validation object + * + */ + + return new RangeDescription(value, row, column, { + tag: 'input', + height: (height) ? height : undefined, + width: (width) ? width : undefined, + font: { family: 'Poppins', color: 'blue', size: 11, weight: 'bold' }, + background: '#d9ead3', + border: { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + }, + validation: (validation) ? validation : undefined, + unprotected: true, + }); + +} + +function Checkbox_(row, column, value, horizontalAlignment, applyBorder, height, width) { + /* + * arguments: [row[, column[, value[, horizontalAlignment[, applyBorder[, height[, width]]]]]]] + * + * row -> integer (>0) + * column -> integer (>0) + * value -> boolean (false by default) + * horizontalAlignment -> selection op string options "left", "right" and "center" to override existing setting + * applyBorder -> boolean, if true border will be applied + * height -> integer (>0) + * width -> integer (>0) + * + */ + + if (value == undefined) value = false; + + return new RangeDescription(value, row, column, { + tag: 'input', + height: (height) ? height : undefined, + width: (width) ? width : undefined, + font: { color: Label_('/').font.color }, + numberFormat: new Formats().auto, + border: (applyBorder) ? { + top: true, + left: true, + bottom: true, + right: true, + color: Title_().font.color, + } : undefined, + alignment: (horizontalAlignment) ? { horizontal: horizontalAlignment } : undefined, + validation: buildValidation(['checkbox']), + unprotected: true, + }); + +} + +function Message_(message, row, column, height, width) { + /* + * arguments: [message[, row[, column[, height[, width]]]]] + * + * message -> string + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * + */ + + return new RangeDescription(message, row, column, { + height: (height) ? height : undefined, + width: (width) ? width : undefined, + font: { family: 'Inconsolata', size: 11, color: '#cccccc' }, + border: { + top: true, + left: true, + bottom: true, + right: true, + color: '#cccccc', + style: SpreadsheetApp.BorderStyle.DOUBLE, + }, + }); + +} + +function Info_(info, row, column, height, width, background, color) { + /* + * arguments: [info[, row[, column[, height[, width]]]]] + * + * info -> string + * row -> integer (>0) + * column -> integer (>0) + * height -> integer (>0) + * width -> integer (>0) + * background -> string, representing color + * color -> string, representing color + * + */ + + return new RangeDescription(info, row, column, { + height: (height) ? height : undefined, + width: (width) ? width : undefined, + font: { family: 'Roboto', color: (color) ? color : '#666666', style: 'italic' }, + background: (background) ? background : undefined, + border: { + top: true, + left: true, + bottom: true, + right: true, + color: '#999999', + style: SpreadsheetApp.BorderStyle.DOTTED, + }, + alignment: { horizontal: 'left' }, + indent: { 'left': 1 }, + }); + +} + +function Separator_(direction, row, column, length) { + /* + * arguments: direction, start, length + * + * direction -> string, either "vertical" or "horizontal" + * row -> integer (>0) + * column -> integer (>0) + * length -> integer (>0), height if vertical, width if horizontal + * + */ + + var separator = new RangeDescription(undefined, row, column, { + mustMerge: false, + border: { + color: '#cccccc', + style: SpreadsheetApp.BorderStyle.SOLID_MEDIUM, + }, + }); + + switch (direction) { + case 'vertical': + separator.height = length; + separator.border.right = true; + break; + case 'horizontal': + separator.width = length; + separator.border.bottom = true; + break; + } + + return separator; + +} + + +/** Misc **/ + +function applyLabel() { + + var u; + + var value = this.label.value; + + var prop; + + switch (this.purpose) { + case 'required': + case 'optional': + case 'action': + case 'checkbox': + prop = 'input'; + break; + default: prop = 'text_range'; + } + + var required = (this.purpose == 'required') ? true : false, + noIndent = (this.purpose == 'checkbox') ? true : false; + + return Label_(value, required, this[prop].row, col(this[prop].column) - 1, u, u, noIndent); + +} + +function applyInput() { + + var u; + + var input; + + var value = (isPropDefined(this.input, 'value')) ? this.input.value : '', + row = this.input.row, + column = col(this.input.column), + height = (isPropDefined(this.input, 'height')) ? this.input.height : u, + width = (isPropDefined(this.input, 'width')) ? this.input.width : u, + validation = (isPropDefined(this.input, 'validation')) ? this.input.validation : u, + numberFormat = (isPropDefined(this.input, 'numberFormat')) ? this.input.numberFormat : u, + horizontalAlignment = (isPropDefined(this.input, 'horizontalAlignment')) ? this.input.horizontalAlignment : u, + applyBorder = (isPropDefined(this.input, 'applyBorder')) ? this.input.applyBorder : u, + unprotected = (isPropDefined(this.input, 'unprotected')) ? this.input.unprotected : u; + + switch (this.purpose) { + case 'required': + input = InputRequired_(value, row, column, height, width, validation, numberFormat, horizontalAlignment, unprotected); + break; + case 'optional': + input = InputOptional_(value, row, column, height, width, validation, numberFormat, horizontalAlignment, unprotected); + break; + case 'action': + input = InputAction_(value, row, column, height, width, validation); + break; + case 'checkbox': + input = Checkbox_(row, column, value, horizontalAlignment, applyBorder, height, width); + break; + } + + return input; + +} + +function applyTextRange() { + + var u; + + var textRange; + + var value = (isPropDefined(this.text_range, 'value')) ? this.text_range.value : '', + row = this.text_range.row, + column = col(this.text_range.column), + height = (isPropDefined(this.text_range, 'height')) ? this.text_range.height : u, + width = (isPropDefined(this.text_range, 'width')) ? this.text_range.width : u, + numberFormat = (isPropDefined(this.text_range, 'numberFormat')) ? this.text_range.numberFormat : u, + horizontalAlignment = (isPropDefined(this.text_range, 'horizontalAlignment')) ? + this.text_range.horizontalAlignment : u, + omitIndent = (isPropDefined(this.text_range, 'omitIndent')) ? this.text_range.omitIndent : u, + background = (isPropDefined(this.text_range, 'background')) ? this.text_range.background : u, + color = (isPropDefined(this.text_range, 'color')) ? this.text_range.color : u; + + switch (this.purpose) { + case 'title': + textRange = Title_(value, row, column, height, width, horizontalAlignment, omitIndent); + break; + case 'header_main': + textRange = HeaderMain_(value, row, column, height, width, horizontalAlignment, omitIndent); + break; + case 'header': + textRange = Header_(value, row, column, height, width, horizontalAlignment, omitIndent); + break; + case 'table_label': + textRange = Label_(value, required, row, column, height, width, omitIndent, true); + break; + case 'cell': + textRange = Cell_(value, row, column, height, width, numberFormat, horizontalAlignment, omitIndent); + break; + case 'message': + textRange = Message_(value, row, column, height, width); + break; + case 'info': + textRange = Info_(value, row, column, height, width, background, color); + break; + } + + return textRange; + +} + + + + +/** + * Updated: 2019-04-16 + */ + + +/** Descriptions **/ + +function RangeDescription(value, row, column, desc) { + /* + * arguments: [value[, row[, column[, desc]]]] + * + * value -> any type + * row -> integer (>0) + * column -> integer (>0) + * desc -> object containing full range description to override default settings + * + */ + + var tag = 'range', + sheetName = '', + row = (row && typeof row == 'number') ? row : 1, + column = (column && typeof column == 'number') ? column : 1, + height = 1, width = 1, + mustMerge = true, // options: true, false, 'vertically', 'across' + wrapStrategy = SpreadsheetApp.WrapStrategy.CLIP, + numberFormat = '@', + background = '#efefef', + validation = null, + value = (value) ? value : '', + protection = false, // options: true, false, 'warning' + unprotected = false; + + if (desc) { + if (isPropDefined(desc, 'tag')) tag = desc.tag; + if (desc.hasOwnProperty('sheetName') && typeof desc.sheetName == 'string') sheetName = desc.sheetName; + if (desc.hasOwnProperty('row') && typeof desc.row == 'number') row = desc.row; + if (desc.hasOwnProperty('column') && typeof desc.column == 'number') column = desc.column; + if (desc.hasOwnProperty('height') && typeof desc.height == 'number') height = desc.height; + if (desc.hasOwnProperty('width') && typeof desc.width == 'number') width = desc.width; + if (isPropDefined(desc, 'mustMerge')) mustMerge = desc.mustMerge; + if (isPropDefined(desc, 'wrapStrategy')) wrapStrategy = desc.wrapStrategy; + if (isPropDefined(desc, 'numberFormat')) numberFormat = desc.numberFormat; + if (isPropDefined(desc, 'background')) background = desc.background; + if (isPropDefined(desc, 'validation')) validation = desc.validation; + if (isPropDefined(desc, 'value')) value = desc.value; + if (isPropDefined(desc, 'protection')) protection = desc.protection; + if (isPropDefined(desc, 'unprotected')) unprotected = desc.unprotected; + } + + function Border() { + + var top = null, left = null, bottom = null, right = null, + vertical = null, horizontal = null, + color = null, style = null; + + if (desc && isPropDefined(desc, 'border')) { + var border = desc.border; + if (isPropDefined(border, 'top')) top = border.top; + if (isPropDefined(border, 'left')) left = border.left; + if (isPropDefined(border, 'bottom')) bottom = border.bottom; + if (isPropDefined(border, 'right')) right = border.right; + if (isPropDefined(border, 'vertical')) vertical = border.vertical; + if (isPropDefined(border, 'horizontal')) horizontal = border.horizontal; + if (isPropDefined(border, 'color')) color = border.color; + if (isPropDefined(border, 'style')) style = border.style; + } + + this.top = top; + this.left = left; + this.bottom = bottom; + this.right = right; + this.vertical = vertical; + this.horizontal = horizontal; + this.color = color; + this.style = style; + + } + + function Alignment() { + + var vertical = 'middle', horizontal = 'center'; + + if (desc && isPropDefined(desc, 'alignment')) { + var alignment = desc.alignment; + if (isPropDefined(alignment, 'vertical')) vertical = alignment.vertical; + if (isPropDefined(alignment, 'horizontal')) horizontal = alignment.horizontal; + } + + this.vertical = vertical; + this.horizontal = horizontal; + + } + + function Font() { + + var family = 'Inconsolata', + color = null, + size = 10, + weight = 'normal', + style = 'normal', + line = 'none'; + + if (desc && isPropDefined(desc, 'font')) { + var font = desc.font; + if (isPropDefined(font, 'family')) family = font.family; + if (isPropDefined(font, 'color')) color = font.color; + if (isPropDefined(font, 'size')) size = font.size; + if (isPropDefined(font, 'weight')) weight = font.weight; + if (isPropDefined(font, 'style')) style = font.style; + if (isPropDefined(font, 'line')) line = font.line; + } + + this.family = family; + this.color = color; + this.size = size; + this.weight = weight; + this.style = style; + this.line = line; + + } + + function Indent() { + + var left = 0, right = 0; + + if (desc && isPropDefined(desc, 'indent')) { + var indent = desc.indent; + if (isPropDefined(indent, 'left')) left = indent.left; + if (isPropDefined(indent, 'right')) right = indent.right; + } + + this.left = left; + this.right = right; + + } + + // Meta + this.tag = tag; + + // Range + this.sheetName = sheetName; + this.row = row; + this.column = column; + this.height = height; + this.width = width; + this.mustMerge = mustMerge; + + // Appearance + this.wrapStrategy = wrapStrategy; + this.numberFormat = numberFormat; + this.background = background; + this.border = new Border; + this.alignment = new Alignment; + this.font = new Font; + this.indent = new Indent; + + // Data + this.validation = validation; + this.value = value; + + // Protection + this.protection = protection; + this.unprotected = unprotected; + +} + +function SheetDescription(height, width, sheetName) { + /* + * arguments: [height[, width[, sheetName]]] + * + * height -> integer (>0) || array of integers(>0) representing different row heights + * width -> integer (>0) || array of integers(>0) representing different column widths + * sheetName -> string + * + */ + + if (!height) height = 1; + if (!width) width = 1; + + var rowHeight = 21, columnWidth = 100; + + if (Array.isArray(height)) { + if (height.every(function(n) { return typeof n == 'number'; })) { + rowHeight = height; + height = height.length; + } + } + + if (Array.isArray(width)) { + if (width.every(function(n) { return typeof n == 'number'; })) { + columnWidth = width; + width = width.length; + } + } + + this.sheetName = (sheetName && typeof sheetName == 'string') ? sheetName : ''; + + // Dimensions + this.height = height; + this.width = width; + this.rowHeight = rowHeight; + this.columnWidth = columnWidth; + this.frozenRows = 0; + this.frozenColumns = 0; + + // Body + this.range = new RangeDescription; + this.range.height = height; + this.range.width = width; + this.range.mustMerge = false; + this.hasGridlines = false; + + // Protection + this.protection = false; + /* must be either false or Protection Object (may be ommitted or deleted) + * Protection Object -> { + * warning -> boolean + * unprotected -> array of unprotected ranges + * } + */ + +} + + +/** Builders **/ + +function buildForm(sheetIdx, formIdx) { + /* + * arguments: sheetIdx, formIdx + * + * sheetIdx -> integer (>=0), representing sheet index in Custom Settings + * formIdx -> integer (>=0), representing form index in list of forms in sheet + * + */ + + var builtForm = new Object; + + var settings = new Settings; + sheetName = Object.keys(settings.sheets)[sheetIdx], + sheetSettings = settings.sheets[sheetName], + formProp = Object.keys(sheetSettings.forms)[formIdx], + form = sheetSettings.forms[formProp], + keys = Object.keys(form), + sheetDesc = new SheetDescription, + rangeDescs = new Array; + + sheetDesc.sheetName = sheetName; + if (isPropDefined(sheetSettings, 'height')) sheetDesc.height = sheetSettings.height; + if (isPropDefined(sheetSettings, 'width')) sheetDesc.width = col(sheetSettings.width); + if (isPropDefined(sheetSettings, 'rowHeight')) sheetDesc.rowHeight = sheetSettings.rowHeight; + if (isPropDefined(sheetSettings, 'columnWidth')) sheetDesc.columnWidth = sheetSettings.columnWidth; + if (isPropDefined(sheetSettings, 'frozenRows')) sheetDesc.frozenRows = sheetSettings.frozenRows; + if (isPropDefined(sheetSettings, 'frozenColumns')) sheetDesc.frozenColumns = col(sheetSettings.frozenColumns); + if (isPropDefined(sheetSettings, 'protection')) sheetDesc.protection = sheetSettings.protection; + + for (var i = 0; i < keys.length; i++) { + if (isPropDefined(form[keys[i]], 'setLabel')) rangeDescs.push(form[keys[i]].setLabel()); + if (isPropDefined(form[keys[i]], 'setInput')) rangeDescs.push(form[keys[i]].setInput()); + if (isPropDefined(form[keys[i]], 'setTextRange')) rangeDescs.push(form[keys[i]].setTextRange()); + } + + if (isPropDefined(sheetSettings, 'separators')) { + var separators = sheetSettings.separators; + if (Array.isArray(separators) && separators.length) + rangeDescs = rangeDescs.concat(separators); + } + + builtForm.sheetDesc = sheetDesc; + builtForm.rangeDescs = rangeDescs; + + return builtForm; + +} + +function buildRange(desc) { + /* + * arguments: desc + * + * desc -> object containing full range description + * + */ + + var ss = SpreadsheetApp.getActive(), + sheet = (desc.sheetName) ? ss.getSheetByName(desc.sheetName) : ss.getActiveSheet(), + range = sheet.getRange(desc.row, desc.column, desc.height, desc.width), + cell = sheet.getRange(desc.row, desc.column); + + var value = desc.value; + if (value && typeof value == 'string') { + var left = desc.indent.left, + right = desc.indent.right; + if (left || right) { + polyfills__String_repeat(); + var lI = (left) ? ' '.repeat(left) : '', + rI = (right) ? ' '.repeat(right) : ''; + value = lI + value + rI; + } + } + + if (desc.mustMerge) { + switch (desc.mustMerge) { + case 'vertically': range.mergeVertically(); break; + case 'across': range.mergeAcross(); break; + default: range.merge(); + } + } else range.breakApart(); + + range + .setBorder( + desc.border.top, desc.border.left, + desc.border.bottom, desc.border.right, + desc.border.vertical, desc.border.horizontal, + desc.border.color, desc.border.style + ) + .setWrapStrategy(desc.wrapStrategy) + .setNumberFormat(desc.numberFormat) + .setHorizontalAlignment(desc.alignment.horizontal) + .setVerticalAlignment(desc.alignment.vertical) + .setBackground(desc.background) + .setFontColor(desc.font.color) + .setFontFamily(desc.font.family) + .setFontSize(desc.font.size) + .setFontLine(desc.font.line) + .setFontWeight(desc.font.weight) + .setFontStyle(desc.font.style) + .setDataValidation(desc.validation); + + cell.setValue(value); + +} + +function buildRanges(descArr) { + /* + * arguments: descArr + * + * descArr -> array of objects containing full range descriptions + * + */ + + if (Array.isArray(descArr)) { + for (var i = 0; i < descArr.length; i++) buildRange(descArr[i]); + } + +} + +function buildSheet(desc, rangeArr, isNew) { + /* + * arguments: desc[, rangeArr[, isNew]] + * + * desc -> object containing full sheet description + * rangeArr -> array of ranges to build + * isNew -> boolean (false by default) + */ + + if (!isNew) isNew = false; + + var ss = SpreadsheetApp.getActive(); + var sheet, unprotected1 = [], unprotected2 = []; + + var sheetName = (desc.sheetName) ? desc.sheetName : undefined, + height = desc.height, + width = desc.width, + rowHeight = desc.rowHeight, + columnWidth = desc.columnWidth, + frozenRows = desc.frozenRows, + frozenColumns = desc.frozenColumns; + + if (isNew) { + (sheetName) ? ss.insertSheet(sheetName) : ss.insertSheet(); + sheet = ss.getActiveSheet(); + } else { + sheet = (sheetName) ? ss.getSheetByName(sheetName) : ss.getActiveSheet(); + } + + shrinkSheet(sheet); + + sheet.setHiddenGridlines(!desc.hasGridlines); + + if (height > 1) sheet.insertRowsAfter(1, height - 1); + if (width > 1) sheet.insertColumnsAfter(1, width - 1); + + if (Array.isArray(rowHeight)) { + for (var r = 0; r < rowHeight.length; r++) { + sheet.setRowHeight(r + 1, rowHeight[r]); + } + } else { + sheet.setRowHeights(1, height, rowHeight); + } + + if (Array.isArray(columnWidth)) { + for (var c = 0; c < columnWidth.length; c++) { + sheet.setColumnWidth(c + 1, columnWidth[c]); + } + } else { + sheet.setColumnWidths(1, width, columnWidth); + } + + if (frozenRows && frozenRows < height) sheet.setFrozenRows(frozenRows); + if (frozenColumns && frozenColumns < width) sheet.setFrozenColumns(frozenColumns); + + if (rangeArr && Array.isArray(rangeArr)) { + + for (var r = 0; r < rangeArr.length; r++) { + var rng = rangeArr[r]; + if (sheetName) rng.sheetName = sheetName; + if (rng.unprotected) { + unprotected1.push(sheet.getRange(rng.row, rng.column, rng.height, rng.width)); + if (unprotected1.length == 1) sheet.setCurrentCell(sheet.getRange(rng.row, rng.column)); + } + } + + buildRanges(rangeArr); + + } + + var protection = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET)[0]; + if (protection) protection.remove(); + + if (desc.hasOwnProperty('protection') && desc.protection) { + + protection = sheet.protect(); + + if (desc.protection.hasOwnProperty('warning') && desc.protection.warning) { + protection.setWarningOnly(true); + } else { + var owner = ss.getOwner(); + protection.addEditor(owner); + protection.removeEditors(protection.getEditors()); + } + + var hasUnprotected = isPropDefined(desc.protection, 'unprotected'); + + if (hasUnprotected || unprotected1.length) { + + if (hasUnprotected && desc.protection.unprotected.length) { + unprotected2 = desc.protection.unprotected; + } + + var unprotected = unprotected1.concat(unprotected2); + + if (unprotected.length) protection.setUnprotectedRanges(unprotected); + + } + + } + +} + +function buildValidation(desc, showArrow, allowInvalid) { + /* + * arguments: desc[, showArrow[, allowInvalid]] + * + * desc -> array of description entities, where index 0 is purpose and others are arguments + * showArrow -> boolean, if true dropdown arrow will appear in dropdown type of validation + * allowInvalid -> boolean, if true will allow validation override + * + */ + + if (desc) { + + var validation = SpreadsheetApp.newDataValidation(), + purpose = desc[0]; + (showArrow == undefined || showArrow == null) ? showArrow = true : showArrow = showArrow; + (allowInvalid == undefined) ? allowInvalid = false : allowInvalid = allowInvalid; + + switch (purpose) { + + // Dates + case 'date': validation.requireDate(); break; + case 'date =': validation = validation.requireDateEqualTo(date(desc[1])); break; + case 'date >': validation = validation.requireDateAfter(date(desc[1])); break; + case 'date >=': validation = validation.requireDateOnOrAfter(date(desc[1])); break; + case 'date <': validation = validation.requireDateBefore(date(desc[1])); break; + case 'date <=': validation = validation.requireDateOnOrBefore(date(desc[1])); break; + case 'date <=>': validation = validation.requireDateBetween(date(desc[1]), date(desc[2])); break; + case 'date <>': validation = validation.requireDateNotBetween(date(desc[1]), date(desc[2])); break; + + // Numbers + case 'number =': validation = validation.requireNumberEqualTo(desc[1]); break; + case 'number !=': validation = validation.requireNumberNotEqualTo(desc[1]); break; + case 'number >': validation = validation.requireNumberGreaterThan(desc[1]); break; + case 'number >=': validation = validation.requireNumberGreaterThanOrEqualTo(desc[1]); break; + case 'number <': validation = validation.requireNumberLessThan(desc[1]); break; + case 'number <=': validation = validation.requireNumberLessThanOrEqualTo(desc[1]); break; + case 'number <=>': validation = validation.requireNumberBetween(desc[1], desc[2]); break; + case 'number <>': validation = validation.requireNumberNotBetween(desc[1], desc[2]); break; + + // Text + case 'text =': validation = requireTextEqualTo(desc[1]); break; + case 'text +': validation = requireTextContallowInvalidns(desc[1]); break; + case 'text -': validation = requireTextDoesNotContallowInvalidn(desc[1]); break; + + // Misc + case 'checkbox': + if (desc[1]) { + if (desc[2]) validation = validation.requireCheckbox(desc[1], desc[2]); + else validation = validation.requireCheckbox(desc[1]); + } else validation = validation.requireCheckbox(); + break; + case 'formula': validation = validation.requireFormulaSatisfied(desc[1]); break; + case 'emallowInvalidl': validation = validation.requireTextIsEmallowInvalidl(); break; + case 'url': validation = validation.requireTextIsUrl(); break; + case 'list': validation = validation.requireValueInList(desc[1], showArrow); break; + case 'range': validation = validation.requireValueInRange(desc[1], showArrow); break; + + } + + validation = validation.setAllowInvalid(allowInvalid).build(); + + return validation; + + } + +} + + +/** Misc **/ + +function shrinkSheet(sheet) { + /* + * arguments: sheet + * + * sheet -> sheet object + * + */ + + var u; + + var maxRows = sheet.getMaxRows(), + maxColumns = sheet.getMaxColumns(), + frozenRows = sheet.getFrozenRows(), + frozenColumns = sheet.getFrozenColumns(); + + if (frozenRows) sheet.setFrozenRows(0); + if (frozenColumns) sheet.setFrozenColumns(0); + if (maxRows > 1) sheet.deleteRows(2, maxRows - 1); + if (maxColumns > 1) sheet.deleteColumns(2, maxColumns - 1); + + desc = new SheetDescription; + sheet.setRowHeight(1, desc.rowHeight); + sheet.setColumnWidth(1, desc.columnWidth); + sheet.setHiddenGridlines(false); + + buildRange(new RangeDescription(u, u, u, { + sheetName: sheet.getName(), + border: { top: false, right: false, bottom: false, left: false }, + font: { color: new RangeDescription().background } + })); + +} + +function ssHasSheetWithName(sheetName, ss) { + /* + * arguments: sheetName[, ss] + * + * sheetName -> string, sheet name to look for in spreadsheet + * ss -> spreadsheet + * + */ + + if (ss == undefined) ss = SpreadsheetApp.getActiveSpreadsheet(); + + var sheets = ss.getSheets(), + hasSheet = false; + + for (var i = 0; i < sheets.length; i++) { + if (sheets[i].getName() == sheetName) { + hasSheet = true; + break; + } + } + + return hasSheet; + +} + + + + +/** + * Updated: 2019-04-12 + */ + + +/** Formats **/ + +function Formats() { + + this.auto = '0.###############'; + this.text = '@'; + this.date = { + reversed: 'yyyy-mm-dd', + us: 'mm/dd/yyyy', + intl: 'dd.mm.yyyy', + month_short: 'mmm d, yyyy', + month_long: 'mmmm d, yyyy', + }; + this.dollar = { + no_text: [ + '$#,##0.00;;;', + '$#,##0.00;($#,##0.00);;', + '$#,##0.00;($#,##0.00);$0.00;' + ], + text_allowed: [ + '$#,##0.00;;', + '$#,##0.00;($#,##0.00);', + '$#,##0.00;($#,##0.00);$0.00' + ], + }; + +} + + +/** Converters **/ + +function convertLocalDateToEST(params) { + /* + * arguments: [params] + * + * paramas -> string representing time parameters + * + */ + + var date; + + if (params != undefined) date = new Date(params); + else date = new Date; + + return new Date(date.toLocaleString('en-US', { timeZone: 'America/New_York' })); + +} + +function convertDateToString(date, format) { + /* + * arguments: date, format + * + * date -> date object to convert to string + * format -> string as date format representation + * + */ + + var dateStr; + + var yyyy = date.getFullYear(), + yy = date.getYear(), + month = date.getMonth(), + calendarDay = date.getDate(), + weekDay = date.getDay(), + hours = date.getHours(), + minutes = date.getMinutes(), + seconds = date.getSeconds(); + + var ap_ = /\bam\/pm\b/gi, ap = 'AM'; + + if (format.search(ap) > -1 && hours > 12) { + hours = hours - 12; + ap = 'PM'; + } + + var months = { + short: [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' + ], + long: [ + 'January', 'February', 'March', + 'April', 'May', 'June', + 'July', 'August', 'September', + 'October', 'November', 'December' + ], + }; + + var weekDays = { + short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + long: [ + 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' + ], + }; + + dateStr = format + .replace(/\byyyy\b/gi, yyyy) + .replace(/\byy\b/gi, yy) + .replace(/\bmmmm\b/gi, months.long[month]) + .replace(/\bmmm\b/gi, months.short[month]) + .replace(/\bmm\b/g, ((month + 1) < 10) ? '0' + (month + 1) : month + 1) + .replace(/\bm\b/gi, month + 1) + .replace(/\bdddd\b/gi, weekDays.long[weekDay]) + .replace(/\bddd\b/gi, weekDays.short[weekDay]) + .replace(/\bdd\b/gi, (calendarDay < 10) ? '0' + calendarDay : calendarDay) + .replace(/\bd\b/gi, calendarDay) + .replace(/\bHH\b/g, (hours < 10) ? '0' + hours : hours) + .replace(/\bMM\b/g, (minutes < 10) ? '0' + minutes : minutes) + .replace(/\bSS\b/g, (seconds < 10) ? '0' + seconds : seconds) + .replace(ap_, ap);; + + return dateStr; + +} + +function convertDateStringReversedToUS(reversedDateStr) { + /* + * arguments: reversedDateStr + * + * reversedDateStr -> string representing date pattern "yyyy-mm-dd" + * + */ + + if ( + reversedDateStr && typeof reversedDateStr == 'string' && + reversedDateStr.length == 10 && + reversedDateStr.search('-') == 4 && + reversedDateStr.lastIndexOf('-') == 7 + ) + { + var d = reversedDateStr.split('-'); + return d[1] + '/' + d[2] + '/' + d[0]; + } + +} + + + + +/** + * Updated: 2019-04-10 + */ + + +/** Simplifiers **/ + +function abc_() { + return [ + , 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm' + , 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' + ]; +} + +function col(ltr_num, isUC) { + /* + * arguments: ltr_num[, isUC] + * + * ltr_num -> string of letters (len<=2) || integer (>0,len<=2) + * isUC -> boolean determining if return column must be presented in upper case + * + */ + + var abc = abc_(); + + if (typeof ltr_num == 'string') { + + var colIdx, + ltr = ltr_num.toLowerCase(); + if (ltr.length == 2) colIdx = abc.indexOf(ltr[0]) * 26 + abc.indexOf(ltr[1]); + else colIdx = abc.indexOf(ltr[0]); + return colIdx; + + } else if (typeof ltr_num == 'number') { + + var ltr, num = ltr_num; + if (num > 26) ltr = abc[quotient(num, 26)] + abc[num % 26]; + else ltr = abc[num]; + if (isUC) ltr = ltr.toUpperCase(); + return ltr; + + } + +} + +function quotient(n1, n2) { + /* + * arguments: n1, n2 + * + * n1 -> integer + * n2 -> integer + * + */ + + return (n1 - (n1 % n2)) / n2; + +} + +function clone(source) { + /* + * arguments: source + * + * source -> object to clone + * + */ + + var target = new Object(); + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + + return target; + +} + +function isPropDefined(source, prop) { + /* + * arguments: source, prop + * + * source -> object to test + * prop -> string representing object's property + * + */ + + return source.hasOwnProperty(prop) && source[prop] != undefined; + +} + + +/** Polyfills **/ + +// String + +function polyfills__String_repeat() { + /* + * source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat + * + */ + + if (!String.prototype.repeat) { + String.prototype.repeat = function(count) { + 'use strict'; + if (this == null) + throw new TypeError('can\'t convert ' + this + ' to object'); + + var str = '' + this; + // To convert string to integer. + count = +count; + // Check NaN + if (count != count) + count = 0; + + if (count < 0) + throw new RangeError('repeat count must be non-negative'); + + if (count == Infinity) + throw new RangeError('repeat count must be less than infinity'); + + count = Math.floor(count); + if (str.length == 0 || count == 0) + return ''; + + // Ensuring count is a 31-bit integer allows us to heavily optimize the + // main part. But anyway, most current (August 2014) browsers can't handle + // strings 1 << 28 chars or longer, so: + if (str.length * count >= 1 << 28) + throw new RangeError('repeat count must not overflow maximum string size'); + + var maxCount = str.length * count; + count = Math.floor(Math.log(count) / Math.log(2)); + while (count) { + str += str; + count--; + } + str += str.substring(0, maxCount - str.length); + return str; + } + } + +}