Search code examples
javascripthtmljsonsortinglocal-storage

Changing js sorting criteria on a set of user created items


I have this code which allows to create, via an input form, a list of items (contracts).

It creates this:

list of sorted items

The items closest to the left are the parent ones and the other are the childs. Well, as it is now, they are sorted due to the date of the "cdate" value, being the later placed on top. The numbers shown (No. 2, No.1, etc) are dynamically assigned just to keep an order.

Example, if the first parent item created has a date, let's say, April the first 2024, and the second is April 12 2024, then the second goes to top and adquires No. 1 and the firstly created goes to second place. With the child items happens the same, they are sorted below the parent which "cdate" value is inmediately older than its own "cdate" value and the same criteria applies to all the childs created between to parent dates. When adding an item you are able to switch which kind of item you are going to create, a parent or a child.

When adding an item you are able to switch which kind of item you are going to create, a parent or a child.

form for parent and for child

Above is the form. At the left, to create a parent item. At the right, switched to create a child item as it is an unique form for both items.

This is the form below.

<ul>
    <li><div class="item-content">
        <div class="item-inner">
            <div class="item-subtitle">¿Suplemento?</div>
            <div class="item-after">
            <label class="label-switch"><b class="hidden">Escoger si es suplemento</b>
                <input id="isSupplement" type="checkbox"/>
                <div class="checkbox"></div>
            </label>
            </div>
            </div>
        </div>
    </li>
    <li>
    <div id="sens2" class="sensitivefield">
    <div class="item-content">
        <div class="item-inner">
            <div class="item-input"><label for="cname">Nombre del Contrato</label>
            <input id="cname" type="text" placeholder="* Nombre del Contrato" required value="{{model.cname}}"/>
            </div>
        </div>
    </div>
    </div>
    </li>
    <li>
    <div id="sens3" class="sensitivefield" style="display:none">
    <div class="item-content">
        <div class="item-inner">
            <div class="item-input"><label for="sname">Nombre del Suplemento</label>
            <input id="sname" type="text" placeholder="* Nombre del Suplemento" required value="{{model.sname}}"/>
            </div>
        </div>
    </div>
    </div>
    </li>
    <li>
    <div class="item-content">
        <div class="item-inner">
            <div class="item-input"><label for="cdate">Fecha</label>
            <input id="cdate" type="date" placeholder="* Fecha" required value="{{model.cdate}}"/>
            </div>
        </div>
    </div>
    </li>
    <li>
    <div class="item-content">
        <div class="item-inner">
            <div class="item-input"><label for="cnumber">Número del Contrato</label>
            <input id="cnumber" type="text" placeholder="* Número del Contrato" required value="{{model.cnumber}}"/>
            </div>
        </div>
    </div>
    </li>
    <li>
        <div class="item-content">
            <div class="item-subtitle">Vigencia</div>
        </div>
    <li>
    <div class="item-content">
        <div class="item-inner">
            <div class="item-input">
            <label for="sdate">Desde</label>
            <input id="sdate" type="date" placeholder="* Desde" required value="{{model.sdate}}"/>
            <label for="ldate">Hasta</label>
            <input id="ldate" type="date" placeholder="* Hasta" required value="{{model.ldate}}"/>
            </div>
        </div>
    </div>
    </li>
    <li><div id="sens1" class="sensitivefield">
        <div class="item-content">Objeto del Contrato</div>
    <div class="item-content">
        <div class="item-inner">
            <div class="item-input"><label class="hidden" for="cobject">Objeto del Contrato</label>
            <textarea class="resizable" id="cobject" placeholder="* Objeto del Contrato"></textarea>
            </div>
        </div>
    </div>
    </div>
    </li>
    <li>
    <div class="item-content">
        <div class="item-inner">
            <div class="item-input"><label for="cvalue">Valor del Contrato</label>
            <input id="cvalue" type="number" step="any" placeholder="* Valor del Contrato" required value="{{model.cvalue}}"/>
            </div>
        </div>
    </div>
    </li>
</ul>

Here is the html code which displays the result:

<ul>
    {{#each apps}}
        <li class="accordion-item">
            <a href="#" class="item-content item-link" aria-label="Ver contrato">
                <div class="item-media">
                    {{#if isSupplement}}<i class="icon simbicon-evolution" style="margin-left:25px" aria-hidden="true"></i>{{/if}}
                    {{#unless isSupplement}}<i class="icon simbicon-contract" aria-hidden="true"></i>{{/unless}}
                </div>
                    {{#unless isSupplement}}<div class="item-inner"><div class="item-title"><b>No. {{idL}}: </b>{{cname}}</div></div></a>{{/unless}}
                    {{#if isSupplement}}<div class="item-inner"><div class="item-title"><b>No. {{idL}}: </b>{{sname}}</div></div></a>{{/if}}
            <div class="accordion-item-content">
            <div class="list-group">
            <div class="item-content">
                <div class="item-inner">
                    <div class="item-subtitle">Fecha: {{cdate}}</div>
                </div>
            </div>
            <div class="item-content">
                <div class="item-inner">
                    <div class="item-subtitle">Número: {{cnumber}}</div>
                </div>
            </div>
            <div class="item-content">
                <div class="item-inner">
                    <div class="item-subtitle">Vigencia:<br/>desde {{sdate}}<br/>hasta {{ldate}}</div>
                </div>
            </div>
            <div class="item-content">
                <div class="item-inner">
                    <div class="item-subtitle">Objeto: {{cobject}}</div>
                </div>
            </div>
            <div class="item-content">
                <div class="item-inner">
                    <div class="item-subtitle">Valor: {{cvalue}}.00 CUP</div>
                </div>
            </div>
            <br/><br/>
        </li>
    {{/each}}
</ul>

This is the js code which sorts the items:

Client.prototype.getFullAppointments2 = function() {
    var theApps=this.contracts;
    var theApps2=[];
    var currentInApp=null;
    var firstTime=true;
    var counterI=0;
    var counterE=0;
    var item=null;
    theApps.sort(appsSortCre); //appsSortCre  appsSortDec
    for (var i = 0, len = theApps.length; i < len; i++) {
        item = theApps[i];
        if (item.isSupplement==0) {
            if (firstTime) {
                firstTime=false;
                currentInApp=item;
            }else{
                counterE=0;
                counterI++;
                currentInApp.idL=counterI;
                theApps2.push(currentInApp);
                currentInApp=item;
            }

        }else{
            counterE++;
            item.idL=counterE;
            theApps2.push(item);
        }
    }

    if ((item!=null)&&(currentInApp!=null)){
        counterI++;
        currentInApp.idL=counterI;
        theApps2.push(currentInApp);
    }
    //invert
    var theApps3=[];
    for (var i = 0, len = theApps2.length; i < len; i++) {
        theApps3.push(theApps2[len-i-1]);
    }
    return theApps3;
};

Let's say that all the values are stored on the Local-Storage.

Right now it works perfectly but I need to change the sorting criteria but keeping the sort by date. I mean, adding an option selector which allows you to choose to which parent a child will be assigned. I know it can be solved using something like the parent id or the parent name, or both, in a way that the selector shows you the parent "cname" for you to choose from he list. On the html part it would be something like this:

<li>
<div class="item-content">
    <div class="item-inner">
        <div class="item-input">
            <label class="hidden" for="pid">Select a parent</label>
            <select name="pid" id="pid">
                <option class="item-inner" value="">Select a parent</option>
                <option class="item-inner" value="parent_id">Parent Name</option>
                <option class="item-inner" value="parent_id">Parent Name</option>
                <option class="item-inner" value="parent_id">Parent Name</option>
                
                ..............................
                
                <option class="item-inner" value="parent_id">Parent Name</option>
            </select>
        </div>
    </div>
</div>
</li>

but I am stucked on the js part.

How to modify the above js code to show in the form a list of all the previosly created parents to choose from in order to assign a child? And once selected, how to assign that child to the chosen parent?

Something like this:

the desired result

Adding some data as I have been asked. This is the data stored at "Local-storage" after the user have added two parent items and three child items; Nadya Obrien (Acton George and Buffy Daniels) and Rajah Cross (Vance Morrison) stored and appended to each parent using date sorting criteria with the "cdate" values.

Notice that the variable "isSupplement" is which states if an item is parent (false) or child (true).The rest of the data is irrelevant here.

    f7Clients:"[{"contracts":[{"id":"1","cdate":"01/04/2024 12:00 am","cname":"Nayda Obrien","sdate":"30/06/1976 12:00 am","ldate":"29/04/2017 12:00 am","ssdate":"Invalid date","sldate":"Invalid date","cvalue":"90","cnumber":"616","cobject":"Quia autem commodo d","sname":"","sobject":"","isSupplement":false},{"id":"2","cdate":"04/04/2024 12:00 am","cname":"","sdate":"Invalid date","ldate":"Invalid date","ssdate":"19/06/1978 12:00 am","sldate":"29/12/2010 12:00 am","cvalue":"59","cnumber":"378","cobject":"","sname":"Buffy Daniels","sobject":"Dolorem culpa eu pr","isSupplement":true},{"id":"3","cdate":"10/04/2024 12:00 am","cname":"","sdate":"Invalid date","ldate":"Invalid date","ssdate":"03/05/1976 12:00 am","sldate":"03/12/2014 12:00 am","cvalue":"89","cnumber":"33","cobject":"","sname":"Acton George","sobject":"Neque ut et enim acc","isSupplement":true},{"id":"4","cdate":"01/05/2024 12:00 am","cname":"Rajah Cross","sdate":"16/05/2023 12:00 am","ldate":"18/10/1977 12:00 am","ssdate":"Invalid date","sldate":"Invalid date","cvalue":"80","cnumber":"887","cobject":"At vero accusantium ","sname":"","sobject":"","isSupplement":false},{"id":"5","cdate":"06/05/2024 12:00 am","cname":"","sdate":"Invalid date","ldate":"Invalid date","ssdate":"12/01/1996 12:00 am","sldate":"12/05/2004 12:00 am","cvalue":"73","cnumber":"242","cobject":"","sname":"Vance Morrison","sobject":"Reprehenderit dolor","isSupplement":true}],}]"

Solution

  • You can define your sort function(s) and order the options and change the inner HTML accordingly:

    var myItems = document.getElementById("my-items");
    var func = document.getElementById("func");
    var direction = document.getElementById("direction");
    
    var sortFunctions = {
        alphabet: function(a, b) {
            return (a.value < b.value) ? -1 : 1;
        },
        numeric: function(a, b) {
            return a.value - b.value;
        },
        alphabetDesc: function(a, b) {
            return sortFunctions.alphabet(b, a);
        },
        numericDesc: function(a, b) {
            return sortFunctions.numeric(b, a);
        }
    };
    
    function reorder(what, func, direction) {
        let items = [];
        for (let option of what.children) {
            items.push({value: option.value, text: option.innerText});
        }
        what.innerHTML = items.sort(sortFunctions[func.value + ((direction.value === "asc") ? "" : "Desc")]).map(item => `<option value="${item.value}">${item.text}</option>`);
    }
    <select id="func" onchange="reorder(myItems, func, direction)">
        <option value="alphabet">Alphabet</option>
        <option value="numeric">Numeric</option>
    </select>
    <select id="direction" onchange="reorder(myItems, func, direction)">
        <option value="asc">Ascending</option>
        <option value="desc">Descending</option>
    </select>
    <select id="my-items">
        <option value="1">1</option>
        <option value="10">10</option>
        <option value="2">2</option>
    </select>

    EDIT

    This is a proof-of-concept with simplistic HTML to illustrate the mechanism you need. As clarified, there are three main problems to solve:

    1. You need to get all the parent data
    2. Show them as selectable options ordered by date and
    3. Once chosen and submitted, to add the child to the chosen parent

    Without further ado:

    let content = [{"contracts":[{"id":"1","cdate":"01/04/2024 12:00 am","cname":"Nayda Obrien","sdate":"30/06/1976 12:00 am","ldate":"29/04/2017 12:00 am","ssdate":"Invalid date","sldate":"Invalid date","cvalue":"90","cnumber":"616","cobject":"Quia autem commodo d","sname":"","sobject":"","isSupplement":false},{"id":"2","cdate":"04/04/2024 12:00 am","cname":"","sdate":"Invalid date","ldate":"Invalid date","ssdate":"19/06/1978 12:00 am","sldate":"29/12/2010 12:00 am","cvalue":"59","cnumber":"378","cobject":"","sname":"Buffy Daniels","sobject":"Dolorem culpa eu pr","isSupplement":true},{"id":"3","cdate":"10/04/2024 12:00 am","cname":"","sdate":"Invalid date","ldate":"Invalid date","ssdate":"03/05/1976 12:00 am","sldate":"03/12/2014 12:00 am","cvalue":"89","cnumber":"33","cobject":"","sname":"Acton George","sobject":"Neque ut et enim acc","isSupplement":true},{"id":"4","cdate":"01/05/2024 12:00 am","cname":"Rajah Cross","sdate":"16/05/2023 12:00 am","ldate":"18/10/1977 12:00 am","ssdate":"Invalid date","sldate":"Invalid date","cvalue":"80","cnumber":"887","cobject":"At vero accusantium ","sname":"","sobject":"","isSupplement":false},{"id":"5","cdate":"06/05/2024 12:00 am","cname":"","sdate":"Invalid date","ldate":"Invalid date","ssdate":"12/01/1996 12:00 am","sldate":"12/05/2004 12:00 am","cvalue":"73","cnumber":"242","cobject":"","sname":"Vance Morrison","sobject":"Reprehenderit dolor","isSupplement":true}],}];
    
    function switchMode() {
        for (let wrapper of document.querySelectorAll("#container, #form")) {
            wrapper.classList[wrapper.classList.contains("active") ? "remove" : "add"]("active");
        }
    }
    
    function getRowTemplate(row) {
        let data = [];
        let ignore = ["isSupplement"];
        for (let key in row) if (ignore.indexOf(key) < 0) data.push({key, value: row[key]});
        return `
            <tr data-id="${row.id}" class="${row.isSupplement ? "supplement" : "parent"}">
                ${data.map(item => `<td data-key="${item.key}">${item.value}</td>`).join("")}
            </tr>
        `;
    }
    
    function f(a, b) {
        return new Date(a.cdate) < new Date(b.cdate) ? -1 : 1;
    }
    
    function generateFormItems(content) {
        let items = [];
        let ignore = ["isSupplement"];
        for (let key in content[0].contracts[0]) {
           if (ignore.indexOf(key) < 0) {
               items.push(key);
           }
        }
        document.getElementById("form").innerHTML = `
            <form>
                <select id="parent-id" name="parent-id">
                    <option value="">Please select</option>
                    ${getParentData(content).sort(f).map(item => `<option value="${item.id}">${item.cname}(${item.cdate})</option>`)}
                </select><br>
                ${items.map(item => `<input type="text" name="${item}" placeholder="${item}"><br>`).join("")}
                <input type="button" onclick="mySubmit(this.parentNode);" value="Click Here">
            </form>
        `;
    }
    
    function refreshContainer(content) {
        let parentID;
        let template = ``;
        for (let row of content[0].contracts) template += getRowTemplate(row);
        document.getElementById("container").innerHTML = `<table>${template}</table>`;
    }
    
    function getParentData(content) {
        return content[0].contracts.filter(item => !item.isSupplement);
    }
    
    function mySubmit(node) {
        let namedItems = node.querySelectorAll("[name]");
        let newObject = {};
        let parentID;
        for (let namedItem of namedItems) {
            if (namedItem.name !== "parent-id") {
                newObject[namedItem.name] = namedItem.value;
            } else {
                newObject.isSupplement = !!namedItem.value;
                parentID = namedItem.value;
            }
        }
        if (!newObject.isSupplement) {
            content[0].contracts.push(newObject);
        } else {
            let parentIndex = -1;
            let nextIndex = -1;
            for (let index = 0; (index < content[0].contracts.length) && (nextIndex < 0); index++) {
                if (content[0].contracts[index].id == parentID) {
                    parentIndex = content[0].contracts[index].id;
                }
                else if ((parentIndex >= -1) && (!content[0].contracts[index].isSupplement)) {
                    nextIndex = index;
                }
            }
            if (parentIndex < 0) {
                content[0].contracts.push(newObject);
            } else {
                content[0].contracts.splice(nextIndex, 0, newObject);
            }
        }
        document.getElementById("container").innerHTML = "";
        refreshContainer(content);
        generateFormItems(content);
    }
    
    refreshContainer(content);
    generateFormItems(content);
    #container:not(.active) {
        display: none;
    }
    
    #form:not(.active) {
        display: none;
    }
    
    .supplement {
        color: green;
    }
    
    .parent {
        color: red;
    }
    <button id="switch" onclick="switchMode();">Switch</button>
    <div id="container" class="active">
    </div>
    <div id="form">
    </div>

    Explanation

    • context is taken from the JSON that was added to the question
    • we have two modes, a content mode and a form mode and switchMode switches the mode, this can be triggered by clicking the corresponding button
    • getRowTemplate generates a tr for the table, you can of course change this to your structure
    • f is just a date sorting function, more precisely this is a function that compares to items by their cdate and this comparator is passed to sort to sort the items. I did not sort the main object, but we can always sort it
    • generateFormItems generates the form's content, including the select and its options and the input elements for the attributes
    • refreshContainer refreshes the container with the newest data, delegating the generation of each row to getRowTemplate
    • getParentData finds the parent-only subset of the data
    • mySubmit builds an object from the received values, then searches for the appropriate place of the content to insert the record into
    • red rows are parents, green rows are children
    • via CSS we only show the active panel and hide the other one
    • the HTML is as simplistic as possible for the example