Skip to content

Remove lodash #800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 87 additions & 10 deletions js/components/checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ Fliplet.FormBuilder.field('checkbox', {
var $vm = this;

// Sort selected options by their index as a checkbox input option
var ordered = _.sortBy(this.value, function(val) {
return _.findIndex($vm.options, function(option) {
var ordered = this.sortBy(this.value, function(val) {
return $vm.options.findIndex(function(option) {
Copy link
Contributor

@Arpanexe Arpanexe Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armine-fliplet Could you please confirm if this change will work? I assume $vm.options doesn’t have a findIndex function unless it is string or Array.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arpanexe type of $vm.options is Array

return (option.id || option.label) === val;
});
});

// Get all options label in array format
var allOptions = _.map(this.options, function(option) {
var allOptions = this.options.map(function(option) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armine-fliplet Same with map function

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arpanexe type of $vm.options is Array

return option.id || option.label;
});

this.selectedAll = _.isEqual(ordered, allOptions);
this.selectedAll = this.isEqual(ordered, allOptions);
}
},
selectedAll: {
Expand All @@ -83,7 +83,7 @@ Fliplet.FormBuilder.field('checkbox', {
this.value = [];
}

if (!_.isEqual(oldValue, this.value)) {
if (!this.isEqual(oldValue, this.value)) {
this.updateValue();
}
}
Expand All @@ -101,12 +101,89 @@ Fliplet.FormBuilder.field('checkbox', {
return rules;
},
methods: {
/**
* Sorts an array based on iteratee function results
* @param {Array} array - array to sort
* @param {Function} iteratee - function to determine sort value
* @returns {Array} sorted array
*/
sortBy: function(array, iteratee) {
return array.slice().sort(function(a, b) {
var valueA = iteratee(a);
var valueB = iteratee(b);

if (valueA < valueB) return -1;
if (valueA > valueB) return 1;

return 0;
});
},

/**
* Performs deep equality comparison
* @param {*} a - first value
* @param {*} b - second value
* @returns {Boolean} true if equal
*/
isEqual: function(a, b) {
if (a === b) return true;
if (a === null || b === null) return false;

if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;

for (var i = 0; i < a.length; i++) {
if (!this.isEqual(a[i], b[i])) return false;
}

return true;
}

if (typeof a === 'object' && typeof b === 'object') {
var keysA = Object.keys(a);
var keysB = Object.keys(b);

if (keysA.length !== keysB.length) return false;

for (var key of keysA) {
if (!Object.prototype.hasOwnProperty.call(b, key) || !this.isEqual(a[key], b[key])) return false;
}

return true;
}

return false;
},

/**
* Creates unique array using comparator function
* @param {Array} array - array to make unique
* @param {Function} comparator - function to compare elements
* @returns {Array} unique array
*/
uniqueWith: function(array, comparator) {
var result = [];
var $vm = this;

array.forEach(function(item) {
var isDuplicate = result.some(function(existing) {
return comparator.call($vm, item, existing);
});

if (!isDuplicate) {
result.push(item);
}
});

return result;
},
Comment on lines +104 to +179
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

Add comprehensive unit tests for new utility methods.

The three new utility methods (sortBy, isEqual, uniqueWith) are critical functions that should be thoroughly tested.

Test scenarios needed:

  • sortBy: Various data types, edge cases, large arrays
  • isEqual: Primitives, nested objects/arrays, circular references, null/undefined
  • uniqueWith: Different comparator functions, empty arrays, duplicate detection

Consider extracting these utilities to a shared module to avoid duplication across components.

🧰 Tools
🪛 Biome (1.9.4)

[error] 112-112: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)


[error] 113-113: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)


[error] 135-135: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'let' instead.

(lint/style/noVar)


[error] 143-143: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)


[error] 144-144: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)


[error] 148-148: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)


[error] 165-165: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)


[error] 166-166: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)


[error] 169-171: Use let or const instead of var.

A variable declared with var is accessible in the whole body of the function. Thus, the variable can be accessed before its initialization and outside the block where it is declared.
See MDN web docs for more details.
Unsafe fix: Use 'const' instead.

(lint/style/noVar)

🤖 Prompt for AI Agents
In js/components/checkbox.js between lines 104 and 179, the new utility methods
sortBy, isEqual, and uniqueWith lack comprehensive unit tests. Create thorough
test cases covering various data types and edge cases for sortBy, including
large arrays; test isEqual with primitives, nested objects and arrays,
null/undefined, and circular references; and for uniqueWith, test with different
comparator functions, empty arrays, and duplicate detection. Additionally,
consider refactoring these utilities into a shared module to prevent code
duplication across components.


updateValue: function() {
var $vm = this;

// Sort selected options by their index as a checkbox input option
var ordered = _.sortBy(this.value, function(val) {
return _.findIndex($vm.options, function(option) {
var ordered = this.sortBy(this.value, function(val) {
return $vm.options.findIndex(function(option) {
return (option.label || option.id) === val;
});
});
Expand Down Expand Up @@ -148,16 +225,16 @@ Fliplet.FormBuilder.field('checkbox', {
var selectedOptions = [];

this.value.forEach(function(value) {
var selectedOption = _.find($vm.options, function(option) {
return (_.has(option, 'label') && _.has(option, 'id')) ? option.id === value : option.label === value;
var selectedOption = $vm.options.find(function(option) {
return (Object.prototype.hasOwnProperty.call(option, 'label') && Object.prototype.hasOwnProperty.call(option, 'id')) ? option.id === value : option.label === value;
});

if (selectedOption) {
selectedOptions.push(selectedOption);
}
});

this.value = selectedOptions.length ? _.uniqWith(this.value, _.isEqual) : [];
this.value = selectedOptions.length ? this.uniqueWith(this.value, this.isEqual) : [];
}

if (!!this.defaultValue) {
Expand Down
3 changes: 2 additions & 1 deletion js/components/customButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ Fliplet.FormBuilder.field('customButton', {
var $vm = this;

Fliplet.FormBuilder.get().then(function(form) {
var button = _.assign({}, _.find($vm.$parent.fields, { name: $vm.name }), { $el: $vm.$el });
var foundField = $vm.$parent.fields.find(function(field) { return field.name === $vm.name; });
var button = Object.assign({}, foundField, { $el: $vm.$el });

if ($vm.buttonAction) {
$vm.buttonAction.context = {
Expand Down
28 changes: 23 additions & 5 deletions js/components/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ Fliplet.FormBuilder.field('file', {
},
computed: {
selectedFileName: function() {
return _.map(this.value, 'name').join(', ');
return this.value.map(function(file) { return file.name; }).join(', ');
},
isValueUrlLink: function() {
return _.some(this.value, function(value) {
return this.value.some(function(value) {
return typeof value === 'string' && Fliplet.Media.isRemoteUrl(value);
});
}
Expand Down Expand Up @@ -86,10 +86,28 @@ Fliplet.FormBuilder.field('file', {
this.selectedFiles.length = 0;
},
methods: {
/**
* Sorts an array based on iteratee function results
* @param {Array} array - array to sort
* @param {Function} iteratee - function to determine sort value
* @returns {Array} sorted array
*/
sortBy: function(array, iteratee) {
return array.slice().sort(function(a, b) {
var valueA = iteratee(a);
var valueB = iteratee(b);

if (valueA < valueB) return -1;
if (valueA > valueB) return 1;

return 0;
});
},

loadFileData: function() {
var $vm = this;
var isFileDataLoaded = false;
var fileIDs = _.map(this.value, function(fileURL) {
var fileIDs = this.value.map(function(fileURL) {
if (typeof fileURL === 'string' && /v1\/media\/files\/([0-9]+)/.test(fileURL)) {
return +fileURL.match(/v1\/media\/files\/([0-9]+)/)[1];
}
Expand All @@ -107,13 +125,13 @@ Fliplet.FormBuilder.field('file', {
files: fileIDs,
fields: ['name', 'url', 'metadata', 'createdAt']
}).then(function(files) {
var newFiles = _.map(files, function(file) {
var newFiles = files.map(function(file) {
file.size = file.metadata.size;

return file;
});

$vm.value = _.sortBy(newFiles, ['name']);
$vm.value = $vm.sortBy(newFiles, function(file) { return file.name; });
}).catch(function() {});
},
showLocalDateFormat: function(date) {
Expand Down
2 changes: 1 addition & 1 deletion js/components/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ Fliplet.FormBuilder.field('image', {
},
onImageClick: function(index) {
var imagesData = {
images: _.map(this.value, function(imgURL) {
images: this.value.map(function(imgURL) {
return { url: imgURL };
}),
options: {
Expand Down
83 changes: 58 additions & 25 deletions js/components/matrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,32 @@ Fliplet.FormBuilder.field('matrix', {
* @returns {String} an ID unique to the row
*/
getOptionId: function(rowIndex, columnIndex, type) {
return _.kebabCase(this.$parent.id + '-' + this.name + '-' + rowIndex + '-' + columnIndex + '-' + type);
return this.toKebabCase(this.$parent.id + '-' + this.name + '-' + rowIndex + '-' + columnIndex + '-' + type);
},

/**
* Converts a string to kebab-case
* @param {String} str - string to convert
* @returns {String} kebab-case string
*/
toKebabCase: function(str) {
return str
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/[\s_]+/g, '-')
.toLowerCase();
},

/**
* Checks if value is empty (null, undefined, empty object, empty array, empty string)
* @param {*} value - value to check
* @returns {Boolean} true if empty
*/
isEmpty: function(value) {
if (value === null || value === undefined) return true;
if (typeof value === 'string' || Array.isArray(value)) return value.length === 0;
if (typeof value === 'object') return Object.keys(value).length === 0;

return false;
},

/**
Expand All @@ -69,7 +94,7 @@ Fliplet.FormBuilder.field('matrix', {
* @returns {String} a name unique to the row
*/
getOptionName: function(rowIndex) {
return _.kebabCase(this.name + '-' + rowIndex);
return this.toKebabCase(this.name + '-' + rowIndex);
},

/**
Expand Down Expand Up @@ -129,7 +154,7 @@ Fliplet.FormBuilder.field('matrix', {

$vm.value = {};

_.forEach(this.rowOptions, function(row) {
this.rowOptions.forEach(function(row) {
$vm.$set($vm.value, row.id || row.label, undefined);
});
},
Expand All @@ -141,22 +166,23 @@ Fliplet.FormBuilder.field('matrix', {
setValue: function() {
var $vm = this;

if ($vm.value === undefined || _.isEmpty($vm.value)) {
if ($vm.value === undefined || this.isEmpty($vm.value)) {
this.setDefaultValue();
} else {
if (typeof $vm.value === 'string') {
$vm.value = JSON.parse($vm.value);
}

_.forIn($vm.value, function(key, value) {
var rowIndex = _.findIndex($vm.rowOptions, function(row) {
return (_.has(row, 'label') && _.has(row, 'id')) ? row.id === value || row.label === value : row.label === value;
Object.keys($vm.value).forEach(function(value) {
var key = $vm.value[value];
var rowIndex = $vm.rowOptions.findIndex(function(row) {
return (Object.prototype.hasOwnProperty.call(row, 'label') && Object.prototype.hasOwnProperty.call(row, 'id')) ? row.id === value || row.label === value : row.label === value;
});

var row = $vm.rowOptions[rowIndex];

var colIndex = _.findIndex($vm.columnOptions, function(col) {
return (_.has(col, 'label') && _.has(col, 'id')) ? col.id === key || col.label === key : col.label === key;
var colIndex = $vm.columnOptions.findIndex(function(col) {
return (Object.prototype.hasOwnProperty.call(col, 'label') && Object.prototype.hasOwnProperty.call(col, 'id')) ? col.id === key || col.label === key : col.label === key;
});

var col = $vm.columnOptions[colIndex];
Expand Down Expand Up @@ -216,22 +242,26 @@ Fliplet.FormBuilder.field('matrix', {
if (val === '') {
val = {};
checkFlag = 'clear';
} else if (!_.isEmpty(val)) {
} else if (!this.isEmpty(val)) {
var result = [];

_.forIn(val, function(value) {
Object.keys(val).forEach(function(key) {
var value = val[key];

if (typeof value !== 'undefined') {
result.push(value);
}
});

if (result.length > 0) {
_.forEach(result, function(col) {
_.find($vm.columnOptions, function(column) {
if (column.label === col || column.id === col) {
columnOpt.push(column);
}
result.forEach(function(col) {
var foundColumn = $vm.columnOptions.find(function(column) {
return column.label === col || column.id === col;
});

if (foundColumn) {
columnOpt.push(foundColumn);
}
});
}

Expand All @@ -256,9 +286,9 @@ Fliplet.FormBuilder.field('matrix', {
onBeforeSubmit: function(data) {
var $vm = this;

_.forIn(data[this.name], function(key, val) {
var row = _.find($vm.rowOptions, function(row) {
return (_.has(row, 'label') && _.has(row, 'id')) ? row.id === val : row.label === val;
Object.keys(data[this.name]).forEach(function(val) {
var row = $vm.rowOptions.find(function(row) {
return (Object.prototype.hasOwnProperty.call(row, 'label') && Object.prototype.hasOwnProperty.call(row, 'id')) ? row.id === val : row.label === val;
});

if (!row) {
Expand All @@ -279,12 +309,15 @@ Fliplet.FormBuilder.field('matrix', {
var validColumns = [];
var $vm = this;

_.forIn(this.value, function(key) {
_.find($vm.columnOptions, function(col) {
if (col.label === key || col.id === col) {
validColumns.push(col);
}
Object.keys(this.value).forEach(function(rowKey) {
var columnKey = $vm.value[rowKey];
var foundCol = $vm.columnOptions.find(function(col) {
return col.label === columnKey || col.id === columnKey;
});

if (foundCol) {
validColumns.push(foundCol);
}
});

return validColumns.length > 0;
Expand All @@ -299,7 +332,7 @@ Fliplet.FormBuilder.field('matrix', {
if (this.required && !this.readonly) {
rules.value.required = function() {
// Check that every row has a non-empty value
return _.every($vm.rowOptions, function(row) {
return $vm.rowOptions.every(function(row) {
return typeof $vm.value[row.id || row.label] !== 'undefined';
});
};
Expand Down
2 changes: 1 addition & 1 deletion js/components/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Fliplet.FormBuilder.field('number', {

value = parseFloat(value);

if (_.isNaN(value)) {
if (isNaN(value)) {
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion js/components/password.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Fliplet.FormBuilder.field('password', {
password += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
}

_.forEach(this.rules, function(value) {
this.rules.forEach(function(value) {
if (!value.test(password)) {
isValid = false;
}
Expand Down
Loading
Loading