Getting Incorrect range height for seemingly no reason? - javascript

I am writing a script to copy and paste a range from one sheet to another. The pasted range size should be reduced by using two functions : one to delete rows with specific values and the other is an aggregate function.
I started getting this error after I introduced the aggregate function The function is basically reducing the array size using the reduce JS function.
I have replicated my problem here and the code is accessible in the script editor.
When I run the script I am getting the following error :
Incorrect range height was 28 but should be 7 (line 36, file "test")
I have no idea why am I getting this error. My aggregate function returns a properly formatted array with the right length.
function append_range(){
var origin_sheet = SpreadsheetApp.openById('1-2ZheMz1p01qwtwY3ghbNjJedYfGXeylnLEjDMCLpMw');//open the file
origin_sheet = origin_sheet.getSheetByName('test');
var rangeStart = 2;
var range = origin_sheet.getRange('A'+ (rangeStart.toString())+':T'+ (origin_sheet.getLastRow()).toString());
var dataFromRange = range.getValues();
var dataFromRangeLength = dataFromRange.length;
var destination_sheet = SpreadsheetApp.openById('1-2ZheMz1p01qwtwY3ghbNjJedYfGXeylnLEjDMCLpMw');
destination_sheet = destination_sheet.getSheetByName('append');
var rowLast = destination_sheet.getLastRow()+1;
Logger.log("row last" + rowLast);
var formattedRange = deleteRows(dataFromRange);
var groups = aggregate(formattedRange);
var aggregates = [];
for(var group in groups)
{
aggregates.push(groups[group]);
}
Logger.log(aggregates);
var formattedRangeLength = aggregates.length;
Logger.log("formattedRangeLength" + formattedRangeLength);
destination_sheet.getRange(rowLast,1,formattedRangeLength, 20).setValues(deleteRows(dataFromRange));
function isDate(sDate) {
if (isValidDate(sDate)) {
sDate = Utilities.formatDate(new Date(sDate), "PST", "yyyy-MM-dd");
}
return sDate;
}
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}
//
function deleteRows(dataRange){//just pass the range in an array and this method will return another array with filtered range
var formatted = dataRange.filter(function(e) {
return e[8]||e[9]||e[10]||e[11]||e[12]||e[13]||e[14]||e[15]||e[16]||e[17]||e[18]||e[19];
});
return formatted;
}
function aggregate(data)
{
var groups = data.reduce(
function(accumulator, previous){
{
var key = previous[1] + previous[3] + previous[5] + previous[6];
var group = accumulator[key];
if(group == null || typeof group == 'undefined')
{
accumulator[key] = previous;
}
else {
var startIndex = 8;
for(var i = startIndex; i < previous.length;i++)
{
group[i] += previous[i];
}
}
return accumulator;
}},
{});
return groups;
}
}

The .setValues() is not setting your aggregates array it is trying to set deleteRows(dataFromRange)
// Change the setValues() to your reduced array
destination_sheet.getRange(rowLast,1,formattedRangeLength, 20).setValues(aggregates);

I think this might work:
var output=deleteRows(dataFromRange));
destination_sheet.getRange(rowLast,1,output.length, output[0].length).setValues(deleteRows(output));
This assumes a non jagged array.

Related

Google Apps Script Filtering

I have some data in google sheet which I want to filter based on a certain criteria and return a corresponding value from another column. Lastly, count the number of elements in the returned column. Here is a sample data:
Sample data
A
B
1
Initials
Application Reference
2
MWB.KBB
1001
3
JET,JJB
1002
4
KBB
100,310,041,005
5
MKGC
1006
6
KBB
1007
Let's say I want to filter the data by searching for "KBB". I want to get all cells that contain the word "KBB" which should be three (3) cells. However, I am only getting two in return. The 1st row that contain two elements in a single cell is not included but it should be included. Lastly, count the elements in the returned column based on the criteria.
Here's the code I have tried:
function filter(){
//opened ss via url
const ws = ss.getSheetByName("Sample");
const range = ws.getRange(2,1,ws.getLastRow() - 1,2).getValues();
const initial = range.map(function(n){return n[0];});
const filtered = initial.filter(filterLogic);
Logger.log(initial); // [MWP, KBB, JET, JJB, KBB, MKGC, KBB]
Logger.log(filtered); // [KBB, KBB]
}
function filterLogic(name){
if(name == "KBB"){
return true;
} else {
return false;
}
}
The above code is only for the criteria. Not included is the counting of elements for the returned value from another column after the filter is applied.
What should I do so I can include the first row that contains the text "KBB" as well in my filtered data. Is there any other way around this?
Code:
function searchForKBB(n = "KBB") {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const osh = ss.getSheetByName("Sheet1");
let o = sh.getRange(1,1,sh.getLastRow(),sh.getLastColumn()).createTextFinder(n).matchEntireCell(false).findAll().map(rg => [rg.getA1Notation()]);
o.unshift(["Ranges"]);
osh.getRange(1,1,o.length,o[0].length).setValues(o)
}
Data:
A
B
1
Initials
Application Reference
2
MWB.KBB
1001
3
JET,JJB
1002
4
KBB
100,310,041,005
5
MKGC
1006
6
KBB
1007
Results:
Ranges
A2
A4
A6
Maybe you can do this:
```
function getAllKBBs(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ss1 = ss.getSheetByName("YOUR_SHEET_NAME");
var range = ss1.getRange(1,1,ss1.getLastRow(),4).getValues();
output = whenTextContains("KBB", range, 1, 1);
Logger.log(output.length);
} ```
where whenTextContains() function is in this repository
https://github.com/NikolaPlusEqual/GoogleAppsScriptFilters/blob/main/Functions
Or, you can copy this into you code and call above function:
function letterToColumn(letter){
var column = 0, length = letter.length;
for (var i = 0; i < length; i++)
{
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
}
return column;
}
////// source for letterToColumn() function :
////// https://stackoverflow.com/questions/21229180/convert-column-index-into-corresponding-column-letter
var setData = {}
function whenTextContains(txt, rng, col, targetCol = 0){
if (typeof col == "number" && typeof txt == "string"){
setData.col = col;
setData.txt = txt;
}
else{
return;
}
var output = rng.filter(wtc);
if(targetCol == 0){
return output;
}
else if(typeof targetCol == "number"){
var result = output.map(function (item) {
return item[targetCol-1];
});
return result;
}
else if(typeof targetCol == "string"){
var targetnum = letterToColumn(targetCol);
var result = output.map(function (item) {
return item[targetnum-1];
});
return result;
}
else{
return;
}
}
function wtc(ar){
var txt = setData.txt;
var col = setData.col - 1;
var str = ar[col].toString();
return str.includes(txt);
}

How to push an entire array in javascript based on conditional content of single array element?

I have created a Google script that pushes data every hour from the Capital Bikeshare API to a Google Sheet, but I have noticed that the way I am currently pulling the data doesn't maintain consistency over time. Here's the code I'm using:
function myFunction() {
// Set the active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentData = ss.getSheetByName("Current");
var historicData = ss.getSheetByName("Historic");
// Fetch API
var stationInfo = UrlFetchApp.fetch('https://gbfs.capitalbikeshare.com/gbfs/en/station_information.json');
var stationStatus = UrlFetchApp.fetch('https://gbfs.capitalbikeshare.com/gbfs/en/station_status.json');
// Get the current date and time
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date+' '+time;
// Parse the JSON reply
var jsonInfo = stationInfo.getContentText();
var dataInfo = JSON.parse(jsonInfo);
var jsonStatus = stationStatus.getContentText();
var dataStatus = JSON.parse(jsonStatus);
// Create the data frame for every BID station
var stationInfo72 = dataInfo["data"]["stations"][69];
var stationStatus72 = dataStatus["data"]["stations"][69];
var stationInfo87 = dataInfo["data"]["stations"][83];
var stationStatus87 = dataStatus["data"]["stations"][83];
var stationInfo330 = dataInfo["data"]["stations"][311];
var stationStatus330 = dataStatus["data"]["stations"][311];
var stationInfo153 = dataInfo["data"]["stations"][143];
var stationStatus153 = dataStatus["data"]["stations"][143];
var stationInfo226 = dataInfo["data"]["stations"][213];
var stationStatus226 = dataStatus["data"]["stations"][213];
var stationInfo365 = dataInfo["data"]["stations"][342];
var stationStatus365 = dataStatus["data"]["stations"][342];
var stationInfo473 = dataInfo["data"]["stations"][446];
var stationStatus473 = dataStatus["data"]["stations"][446];
var outputStationsInfo = [stationInfo72, stationInfo87, stationInfo330, stationInfo153, stationInfo226, stationInfo365, stationInfo473]
var outputStationsStatus = [stationStatus72, stationStatus87, stationStatus330, stationStatus153, stationStatus226, stationStatus365, stationStatus473]
Logger.log(outputStationsInfo, outputStationsStatus)
// Create lists of each element
var outputHead = [];
var outputTail = [];
outputStationsInfo.forEach(function(elem,i) {
outputHead.push([elem["station_id"],elem["name"],elem["capacity"], elem["lat"], elem["lon"]]);
});
outputStationsStatus.forEach(function(elem,i) {
outputTail.push([elem["num_bikes_available"], elem["num_ebikes_available"], dateTime]);
});
// Publish arrays in the Current sheet
currentData.getRange(2,1,7,5).setValues(outputHead);
currentData.getRange(2,6,7,3).setValues(outputTail);
// Publish arrays in the Historic sheet
historicData.getRange(historicData.getLastRow() + 1,1,7,5).setValues(outputHead);
historicData.getRange(historicData.getLastRow() - 6,6,7,3).setValues(outputTail);
}
Essentially, I am drilling into the 69th item in the indexes of the JSONs to get the data that I need from two different APIs, and then I merge them together to create a data frame of everything I need to push to the sheet. However, sometimes the API does not report them in the normal order and I end up getting bikeshare stations that aren't in my study area. For example, 99% of the time the 69th item in the array is station_id = 72, but occasionally it's station_id = 73 or something.
Is there a way to conditionally pull a specific array based on the station_id number within the array? I feel like the answer might allow me to do a loop as well to clean this up. Any advice is helpful, as I'm super new to this.
You have to check if the element's station_id is as expected. If not, check through the surrounding parts of the array using a custom iterator.
Snippet:
/**
* #return indexes of the surrounding ``i`` in batches of 5
*/
function* checkSurroundings(i, lastIndex) {
let j = i;
function* check(ct, border, reverse = true, limit = border < 5 ? border : 5) {
const margin = reverse ? ct - limit : ct + limit;
while (ct - margin !== 0) yield reverse ? --ct : ++ct;
return ct;
}
while (i !== 0 || j < lastIndex) {
if (i !== 0) i = yield* check(i, i);
if (j < lastIndex) j = yield* check(j, lastIndex - j, false);
//console.log({ i, j });
}
}
var stations = dataInfo["data"]["stations"];
var stationInfo72 = stations[69];
const iter = checkSurroundings(69,stations.length-1)
//if station_id is not 72, loop through the surrounding indexes
while(stationInfo72["station_id"] !== 72){
const next = iter.next();
if(next.done) {
console.error("station id 72 not found");
break;
}
stationInfo72 = stations[next.value]
}
Snippet showing how checkSurroundings iterates:
/**
* #return indexes of the surrounding ``i`` in batches of 5
*/
function* checkSurroundings(i, lastIndex) {
let j = i;
function* check(ct, border, reverse = true, limit = border < 5 ? border : 5) {
const margin = reverse ? ct - limit : ct + limit;
while (ct - margin !== 0) yield reverse ? --ct : ++ct;
return ct;
}
while (i !== 0 || j < lastIndex) {
if (i !== 0) i = yield* check(i, i);
if (j < lastIndex) j = yield* check(j, lastIndex - j, false);
console.log({ i, j });
}
}
console.log("Order of iteration",[...checkSurroundings(50, 100)])
Conditionally picking elements: filter
For conditionally picking elements from an array in JavaScript, Array.prototype.filter should always be a consideration.
Create a predicate function that matches the shape of your data and checks for certain station IDs.
Here is a function that returns a predicate function. You put in the IDs you want in an array, and it returns the required function for filter.
function byStationId(stationIds) {
return function (obj) {
return stationIds.indexOf(obj.station_id) > -1;
};
}
var myStationFilter = byStationId([72, 73, 74]);
var outputStationsInfo = dataInfo.data.stations.filter(myStationFilter);
Transforming data: map
The pattern
var newArray = [];
oldArray.forEach(function (item) {
newArray.push(/* something based on item */);
});
can usually be replaced with Array.prototype.map
var newArray = oldArray.map(function (item) { return /* something based on item */});
Think of this as the "adapter" from one data shape to another.
function cleanInfo(info) {
return [info.station_id, info.name, info.capacity, info.lat, info.lon];
}
var outputHead = outputStationsInfo.map(cleanInfo);
For the dateTime injection, just do the same trick demonstrated above with the station IDs: have a function that takes a date string and returns the appropriate adapter function.
(Also note the provided date formatting utility Apps Scripts provides, Utilities.formatDate())
var dateTime = Utilities.formatDate(
new Date(),
ss.getSpreadsheetTimeZone(),
"yyyy-MM-dd HH:mm:ss"
);
function cleanStatus(dateTime) {
return function (status) {
return [status.num_bikes_available, status.num_bikes_available, dateTime];
};
}
var outputTail = outputStationsStatus.map(cleanStatus(dateTime));
Here's everything together, untested, just for inspiration. You must at the very least update the line with the station IDs to match your desired station IDs. Note that the helper functions for map and filter are at the bottom, taking advantage of JavaScript's hoisting feature.
function myFunction() {
// Set the active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get the current date and time
var dateTime = Utilities.formatDate(
new Date(),
ss.getSpreadsheetTimeZone(),
"yyyy-MM-dd HH:mm:ss"
);
// Fetch API
var stationInfo = UrlFetchApp.fetch(
"https://gbfs.capitalbikeshare.com/gbfs/en/station_information.json"
);
var stationStatus = UrlFetchApp.fetch(
"https://gbfs.capitalbikeshare.com/gbfs/en/station_status.json"
);
// Parse the JSON reply
var dataInfo = JSON.parse(stationInfo.getContentText());
var dataStatus = JSON.parse(stationStatus.getContentText());
// Create the data frame for every BID station
var myStationFilter = byStationId([72, 73, 74]); //!! UPDATE THESE NUMBERS
var outputStationsInfo = dataInfo.data.stations.filter(myStationFilter);
var outputStationsStatus = dataStatus.data.station.filter(myStationFilter);
// Create lists of each element
var outputHead = outputStationsInfo.map(cleanInfo);
var outputTail = outputStationsStatus.map(cleanStatus(dateTime));
// Publish arrays in the Current sheet
var currentData = ss.getSheetByName("Current");
currentData.getRange(2, 1, 7, 5).setValues(outputHead);
currentData.getRange(2, 6, 7, 3).setValues(outputTail);
// Publish arrays in the Historic sheet
var historicData = ss.getSheetByName("Historic");
historicData
.getRange(historicData.getLastRow() + 1, 1, 7, 5)
.setValues(outputHead);
historicData
.getRange(historicData.getLastRow() - 6, 6, 7, 3)
.setValues(outputTail);
//-------- helper functions ------------
function byStationId(stationIds) {
return function (obj) {
return stationIds.indexOf(obj.station_id) > -1;
};
}
function cleanInfo(info) {
return [info.station_id, info.name, info.capacity, info.lat, info.lon];
}
function cleanStatus(dateTime) {
return function (status) {
return [status.num_bikes_available, status.num_bikes_available, dateTime];
};
}
}

Why can't I ready this Object properties?

I have been trying to figure this out for a couple hours now.
I am receiving a JSON post (Shopify), I'm working in app script via doPost(e) web app, but maybe that's irrelevant.
Here's how it looks when received
notice the weird backslashes in postData.contents (it was stringified to show in my console)...
{"contentLength":6450,"queryString":"","postData":{"contents":"{\"id\":820982911946154508,\"
// clipped because stackoverflow doesn't like it
Here are a bunch of things I've tried
e.postData.contents;
// => {"id":820982911946154508,"email":"jon#doe.ca",...} (Clipped here too)
//Great, looks like a proper object...BUT...
var order = e.postData.contents;
//order.id => undefined
var order = JSON.parse(e.postData.contents);
//order.id => undefined
var order = JSON.parse(JSON.stringify(e.postData.contents));
//order.id => undefined
This is how I convert all numbers over 9 digits to strings
Not the best, maybe if a substring contains commas or colons that surround a number, it will break it.
function testStringNum () {
var test = {"id":820982911946154500,"email":"jon#doe.ca","closed_at":null,"created_at":"2020-07-01T10:01:14-07:00","updated_at":"2020-07-01T10:01:14-07:00","number":234 };
var result = jsonStringNums(test);
}
function jsonStringNums(obj) {
var objString = JSON.stringify(obj);
var objArray = objString.split(/[:,]+/);
for (let index in objArray) {
var regExp = new RegExp(/^(?=.*\d)[\d ]+$/);
var isNum = regExp.test(objArray[index]);
if (isNum && objArray[index].length >= 9) {
objString = objString.replace(objArray[index], '"'+objArray[index]+'"');
}
}
return JSON.parse(objString);
}
Had to handle duplicate data in the array, so this is better
function jsonStringNums(obj) {
var objString = JSON.stringify(obj);
var objArrayAll = objString.split(/[:,]+/);
var objArray = objArrayAll.reduce((unique,item) => {
return unique.includes(item) ? unique : [...unique,item] }, []);
var rxAllNumAndSpaces = new RegExp(/^(?=.*\d)[\d ]+$/);
for (let index in objArray) {
var isNum = rxAllNumAndSpaces.test(objArray[index]);
if (isNum && objArray[index].length >= 8) {
var replacement = '\"'+objArray[index]+'\"';
var rxAllVarInsts = new RegExp(objArray[index], 'g');
objString = objString.replace(rxAllVarInsts, replacement);
}
}
try {
return JSON.parse(objString);
} catch (err) { Logger.log('you done messed up your object '+err);return obj; }
}

I want to generate unique alphabetical keys in alphabetical order |angularJs

I want to generate keys in alphabetical order in such a way that it begins with
aaaaa
then the next would be aaaab and after reaching to aaaaz the string should be aaaba and then aaabb and so on , so that the keys generate properly
My sample JSON to be created
var keygen={aaaaa,
aaaab,
aaaac .........aaaaz,aaaba ....}
my javascript
$scope.doKeyGen=function(lastValueInJSON)
{ // Do something
}
This will work for you. JS Fiddle is here.
https://jsfiddle.net/3d789okv/7/
Make sure that you the last value you give will be hit.
Otherwise you are going to an infinite loop hell.
Also you can configure the number of letters in the call to getNext().
But make sure that you set the equal number of letters in the first value and "aaaaa" and the last value "asxas"
String.prototype.replaceAt=function(index, replacement) {
return this.substr(0, index) + replacement+ this.substr(index + replacement.length);
}
var json = [];
function getNext(charCount,lastValue){
changeIndex = charCount -1;
var newValue = "";
while (changeIndex >= 0){
if(lastValue[changeIndex] !== "z"){
var changed = lastValue[changeIndex];
var replacechanged = String.fromCharCode(changed.charCodeAt(0)+1);
newValue = lastValue.replaceAt(changeIndex,replacechanged)
for(var j=changeIndex+1; j < charCount; ++j){
newValue = newValue.replaceAt(j,"a");
}
return newValue;
}
changeIndex--;
}
}
function createJSON(lastValue){
if(!json.length){
//var startPrefix = "aaaaa";
json.push("aaaaa");
while(lastValue !== json[json.length-1]){
json.push(getNext(5,json[json.length-1]));
}
console.log(json);
}
}
createJSON("aaabz");
You need to use recursive function to generate your keys.
I've written some piece of code in this fiddle link, which generate keys as per your requirement and create JSON too.
Please note I assume small case alphabets keys only. and used 3 length string (aaa), You can use 4 length also but performance degrades.
You can change any first key in input in attached fiddle, like 'aay' then code generate next all possible keys.(aaz, aba,.....,zzz).
You can use this
function getNextKey(lastKeyCode, changeIndex)
{
var charCodes = [];
if( changeIndex == undefined )
changeIndex = lastKeyCode.length - 1;
if(changeIndex - 1 > -1 && lastKeyCode.charCodeAt(changeIndex) == 122 )
{
lastKeyCode = getNextKey(lastKeyCode, changeIndex - 1);
}
lastKeyCode.split('').forEach(function(e){charCodes.push(e.charCodeAt())});
charCodes[changeIndex] = 97 + (charCodes[changeIndex] - 96 ) % 26;
return String.fromCharCode.apply(0, charCodes);
}
//-------------------EDIT ( GENERATE KEYS LIKE THIS )------------
function generateKeys(lastKey)
{
var json = [];
var nextKey = new Array(lastKey.length + 1 ).join('a');
json.push(nextKey);
while( nextKey != lastKey )
{
json.push( (nextKey = getNextKey(nextKey)) )
}
return json;
}
//---------------------------Example----------------------------
var last = 'test';
console.log('Last Key : '+last+' | Generated key length : '+generateKeys(last).length);

Javascript sort and order

So i have this array
[ 'vendor/angular/angular.min.js',
'vendor/angular-nice-bar/dist/js/angular-nice-bar.min.js',
'vendor/angular-material/modules/js/core/core.min.js',
'vendor/angular-material/modules/js/backdrop/backdrop.min.js',
'vendor/angular-material/modules/js/dialog/dialog.min.js',
'vendor/angular-material/modules/js/button/button.min.js',
'vendor/angular-material/modules/js/icon/icon.min.js',
'vendor/angular-material/modules/js/tabs/tabs.min.js',
'vendor/angular-material/modules/js/content/content.min.js',
'vendor/angular-material/modules/js/toolbar/toolbar.min.js',
'vendor/angular-material/modules/js/input/input.min.js',
'vendor/angular-material/modules/js/divider/divider.min.js',
'vendor/angular-material/modules/js/menu/menu.min.js',
'vendor/angular-material/modules/js/select/select.min.js',
'vendor/angular-material/modules/js/radioButton/radioButton.min.js',
'vendor/angular-material/modules/js/checkbox/checkbox.min.js',
'vendor/angular-material/modules/js/switch/switch.min.js',
'vendor/angular-material/modules/js/tooltip/tooltip.min.js',
'vendor/angular-material/modules/js/toast/toast.min.js',
'vendor/angular-clipboard/angular-clipboard.js',
'vendor/angular-animate/angular-animate.min.js',
'vendor/angular-aria/angular-aria.min.js',
'vendor/angular-messages/angular-messages.min.js',
'vendor/angular-ui-router/release/angular-ui-router.js',
'src/app/about/about.js',
'src/app/hekate.cfg.js',
'src/app/hekate.ctrl.js',
'src/app/hekate.module.js',
'src/app/home/home.js',
'src/app/user/dialog/user.signIn.ctrl.js',
'src/app/user/dialog/user.signIn.module.js',
'src/app/user/user.cfg.js',
'src/app/user/user.ctrl.js',
'src/app/user/user.module.js',
'src/common/services/toast.service.js',
'templates-common.js',
'templates-app.js'
]
And taking the following part from the above array as example:
[
'src/app/hekate.cfg.js',
'src/app/hekate.ctrl.js',
'src/app/hekate.module.js',
]
I want to sort it like
[
'src/app/hekate.module.js',
'src/app/hekate.cfg.js',
'src/app/hekate.ctrl.js',
]
So more specific of what i want is to find in that array where string is duplicated and after check if has at the end [.cfg.js, .ctrl.js, .module.js] and automatic order them to [.module.js, .cfg.js, .ctrl.js]
Can anyone please help me with that?
A single sort proposal.
var array = ['src/app/about/about.js', 'src/app/hekate.cfg.js', 'src/app/hekate.ctrl.js', 'src/app/hekate.module.js', 'src/app/home/home.js', 'src/app/user/dialog/user.signIn.ctrl.js', 'src/app/user/dialog/user.signIn.module.js', 'src/app/user/user.cfg.js', 'src/app/user/user.ctrl.js', 'src/app/user/user.module.js'];
array.sort(function (a, b) {
function replaceCB(r, a, i) { return r.replace(a, i); }
var replace = ['.module.js', '.cfg.js', '.ctrl.js'];
return replace.reduce(replaceCB, a).localeCompare(replace.reduce(replaceCB, b));
});
document.write('<pre>' + JSON.stringify(array, 0, 4) + '</pre>');
To prevent so much replaces, i suggest to have a look to sorting with map.
You can try something like this:
Algo:
Group based on path and store file names as value.
Check for existence of one of special file ".cfg.js"
Sort following list based on custom sort.
Loop over object's property and join key with values to form full path again.
If you wish to sort full array, you can sort keys itself and then merge path with names. I have done this. If you do not wish to do this, just remove sort function from final loop.
Sample
var data=["vendor/angular/angular.min.js","vendor/angular-nice-bar/dist/js/angular-nice-bar.min.js","vendor/angular-material/modules/js/core/core.min.js","vendor/angular-material/modules/js/backdrop/backdrop.min.js","vendor/angular-material/modules/js/dialog/dialog.min.js","vendor/angular-material/modules/js/button/button.min.js","vendor/angular-material/modules/js/icon/icon.min.js","vendor/angular-material/modules/js/tabs/tabs.min.js","vendor/angular-material/modules/js/content/content.min.js","vendor/angular-material/modules/js/toolbar/toolbar.min.js","vendor/angular-material/modules/js/input/input.min.js","vendor/angular-material/modules/js/divider/divider.min.js","vendor/angular-material/modules/js/menu/menu.min.js","vendor/angular-material/modules/js/select/select.min.js","vendor/angular-material/modules/js/radioButton/radioButton.min.js","vendor/angular-material/modules/js/checkbox/checkbox.min.js","vendor/angular-material/modules/js/switch/switch.min.js","vendor/angular-material/modules/js/tooltip/tooltip.min.js","vendor/angular-material/modules/js/toast/toast.min.js","vendor/angular-clipboard/angular-clipboard.js","vendor/angular-animate/angular-animate.min.js","vendor/angular-aria/angular-aria.min.js","vendor/angular-messages/angular-messages.min.js","vendor/angular-ui-router/release/angular-ui-router.js","src/app/about/about.js","src/app/hekate.cfg.js","src/app/hekate.ctrl.js","src/app/hekate.module.js","src/app/home/home.js","src/app/user/dialog/user.signIn.ctrl.js","src/app/user/dialog/user.signIn.module.js","src/app/user/user.cfg.js","src/app/user/user.ctrl.js","src/app/user/user.module.js","src/common/services/toast.service.js","templates-common.js","templates-app.js"];
// Create groups based on path
var o = {};
data.forEach(function(item) {
var lastIndex = item.lastIndexOf('/') + 1;
var path = item.substring(0, lastIndex);
var fname = item.substring(lastIndex);
if (!o[path]) o[path] = [];
o[path].push(fname);
});
var manualOrder= [".module.js", ".cfg.js", ".ctrl.js"];
Array.prototype.fuzzyMatch = function(search){
return this.some(function(item){
return item.indexOf(search)>-1;
});
}
Array.prototype.fuzzySearchIndex = function(search){
var pos = -1;
this.forEach(function(item, index){
if(search.indexOf(item)>-1){
pos = index;
}
});
return pos;
}
function myCustomSort(a,b){
var a_pos = manualOrder.fuzzySearchIndex(a);
var b_pos = manualOrder.fuzzySearchIndex(b);
return a_pos > b_pos ? 1 : a_pos < b_pos ? -1 : 0;
}
// Check for ".cfg.js" and apply custom sort
for (var k in o) {
if (o[k].fuzzyMatch(".cfg.js")) {
o[k].sort(myCustomSort);
}
}
// Merge Path and names to create final value
var final = [];
Object.keys(o).sort().forEach(function(item) {
if (Array.isArray(o[item])) {
final = final.concat(o[item].map(function(fn) {
return item + fn
}));
} else
final = final.concat(o[item]);
});
console.log(final);
First make an array for names like 'hekate'.
Then make an array for final results.
We need 3 searching loops for ctrls, cfgs and modules.
If string contains arrayWithNames[0] + '.module' push the whole record to new array that you created. Same with ctrls and cfgs.
var allItems = []; //your array with all elements
var namesArray = [];
var finalResultsArray = [];
//fill name array here:
for(var i=0; i<=allItems.length; i++){
//you have to split string and find the module name (like 'hekate'). i hope you know how to split strings
}
//sort by modules, cfgs, ctrls:
for(var i=0; i<=namesArray.length; i++){
if(allItems[i].indexOf(namesArray[i] + '.module') > -1) {
finalResultsArray.push(allItems[i]);
}
}
for(var i=0; i<=namesArray.length; i++){
if(allItems[i].indexOf(namesArray[i] + '.cfg') > -1) {
finalResultsArray.push(allItems[i]);
}
}
for(var i=0; i<=namesArray.length; i++){
if(allItems[i].indexOf(namesArray[i] + '.ctrl') > -1) {
finalResultsArray.push(allItems[i]);
}
}
//now finalResultsArray have what you wanted
You can provide your own compare function to array.sort (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
Write one that returns the correct order for modules, ctrls and cfgs:
It should first remove the suffixes, and if the rest is the same, use the correct logic to return the order according to the suffix. Otherwise return a value according to the alphabetical order.
Update
I didn't test this code (not is it finished), but it should look something like that:
arr.sort(function(a, b) {
if ((a.endsWith(".cfg.js") || a.endsWith(".ctrl.js") || a.endsWith(".module.js")) &&
(b.endsWith(".cfg.js") || b.endsWith(".ctrl.js") || b.endsWith(".module.js"))) {
var sortedSuffixes = {
".module.js": 0,
".cfg.js": 1,
".ctrl.js": 2
};
var suffixAIdx = a.lastIndexOf(".cfg.js");
if (suffixAIdx < 0) suffixAIdx = a.lastIndexOf(".ctrl.js");
if (suffixAIdx < 0) suffixAIdx = a.lastIndexOf(".module.js");
var suffixBIdx = b.lastIndexOf(".cfg.js");
if (suffixBIdx < 0) suffixBIdx = b.lastIndexOf(".ctrl.js");
if (suffixBIdx < 0) suffixBIdx = b.lastIndexOf(".module.js");
var prefixA = a.substring(0, suffixAIdx);
var prefixB = b.substring(0, suffixAIdx);
if (prefixA != prefixB)
{
return a.localeCompare(b);
}
var suffixA = a.substring(suffixAIdx);
var suffixB = b.substring(suffixBIdx);
return sortedSuffixes[suffixA] - sortedSuffixes[suffixB];
} else {
return a.localeCompare(b);
}
});
Update 2
Here is a fiddle (https://jsfiddle.net/d4fmc7ue/) that works.

Categories

Resources