Skip to content
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

Little change in stjs functionality #27

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from

Conversation

JakubMifek
Copy link

Hi, I was working on one of my projects and came across your st.js library. I found it very useful, the best I could find. There were few issues that I had to deal with though and to be fair I thought I might as well offer you to include my improvements to your library.

What I changed:

NOTE: Maybe some of this functionality had a workaround but I just felt the need for a nice clean solution.

Let function

I don't know if it was done on purpose or you just did not think it through, but after let statement ended, there was no memory cleanup. So let statement behaved more like JavaScript var keyword then let keyword. I do not think it was intended since local scope variable makes more sense to me. If you need a global scope variable you can just declare it at the beginning of the template. And so I added memory cleanup and memory reverse in case of overwriting already existing variable. This also raised an issue in each function where item's data were overwritten with memory data. So I made few tweaks there as well.

If the previous functionality was intended I'd recommend introducing var function for that purpose.

Usage:

const ST = require('stjs');

let data = {
  objects: [{
    name: 'Jakub',
    surname: 'Mifek',
    age: 23
  }, {
    name: 'Some',
    surname: 'Dummy',
    company: 'Some company'
  }, {
    name: 'Lojza',
    surname: 'Smolař',
    age: 50
  }]
};

let template = {
  beforelet: {
    "{{ #each objects }}": {
      name: "{{ name }} {{ surname }}",
      company: "{{ #? company }}",
      role: [{
        "{{ #if surname == 'Mifek' }}": 'Developer'
      }, {
        "{{ #elseif surname == 'Smolař' }}": 'Punching Bag'
      }, {
        "{{ #else }}": undefined
      }],
      age: "{{ #? age }}"
    }
  },
  employees: {
    "{{ #let }}": [{
      company: 'Best company'
    }, {
      "{{ #each objects }}": {
        name: "{{ name }} {{ surname }}",
        company: "{{ #? company }}",
        role: [{
          "{{ #if surname == 'Mifek' }}": 'Developer'
        }, {
          "{{ #elseif surname == 'Smolař' }}": 'Punching Bag'
        }, {
          "{{ #else }}": undefined
        }],
        age: "{{ #? age }}"
      }
    }]
  },
  afterlet: {
    "{{ #each objects }}": {
      name: "{{ name }} {{ surname }}",
      company: "{{ #? company }}",
      role: [{
        "{{ #if surname == 'Mifek' }}": 'Developer'
      }, {
        "{{ #elseif surname == 'Smolař' }}": 'Punching Bag'
      }, {
        "{{ #else }}": undefined
      }],
      age: "{{ #? age }}"
    }
  }
};

console.log(
  JSON.stringify(
    ST.transform(template, data),
    null, 2));

/*
Prints:

{
  "beforelet": [
    {
      "name": "Jakub Mifek",
      "role": "Developer",
      "age": 23
    },
    {
      "name": "Some Dummy",
      "company": "Some company"
    },
    {
      "name": "Lojza Smolař",
      "role": "Punching Bag",
      "age": 50
    }
  ],
  "employees": [
    {
      "name": "Jakub Mifek",
      "company": "Best company",
      "role": "Developer",
      "age": 23
    },
    {
      "name": "Some Dummy",
      "company": "Best company"
    },
    {
      "name": "Lojza Smolař",
      "company": "Best company",
      "role": "Punching Bag",
      "age": 50
    }
  ],
  "afterlet": [
    {
      "name": "Jakub Mifek",
      "role": "Developer",
      "age": 23
    },
    {
      "name": "Some Dummy",
      "company": "Some company"
    },
    {
      "name": "Lojza Smolař",
      "role": "Punching Bag",
      "age": 50
    }
  ]
}
 */

Optional function

I found existential operator not good enough since I came across cases where I needed to dismiss an attribute when the result of its value was either an empty array or an empty object. For that purpose I implemented the optional function. It dismisses entire attribute if the given value is:

  • undefined
  • null
  • false
  • empty array
  • empty object

Usage:

const ST = require('stjs');

let data = {
  objects: [{
    name: 'Jakub',
    surname: 'Mifek'
  }, {
    name: 'Some',
    surname: 'Dummy'
  }, {
    name: 'Lojza',
    surname: 'Smolař'
  }]
};

let template = {
  employees: {
    "{{ #each objects }}": {
      name: "{{ name }} {{ surname }}",
      company: "DCOS s.r.o.",
      role: [{
        "{{ #if surname == 'Mifek' }}": 'Developer'
      }, {
        "{{ #elseif surname == 'Smolař' }}": 'Punching Bag'
      }, {
        "{{ #else }}": undefined
      }],
      "{{ #optional secret }}": [{
        "{{ #if name == 'Jakub' && surname == 'Mifek' }}": 'Plays with trains'
      }]
    }
  }
};

console.log(
  JSON.stringify(
    ST.transform(template, data),
    null, 2));

/*
Prints:

{
  "employees": [
    {
      "name": "Jakub Mifek",
      "company": "DCOS s.r.o.",
      "role": "Developer",
      "secret": "Plays with trains"
    },
    {
      "name": "Some Dummy",
      "company": "DCOS s.r.o."
    },
    {
      "name": "Lojza Smolař",
      "company": "DCOS s.r.o.",
      "role": "Punching Bag"
    }
  ]
}
 */

Flatten function

I also needed a function that would take an array of arrays and merged them all together, similar to concat but with just one parameter.

Usage:

const ST = require('stjs')

let data = {
  "items": [ [ 1, 2, 3 ], [ 4, 5, 6 ], 7 ]
};

let template = {
  "items1": {
    "{{ #flatten }}": "{{ items }}"
  },
  "items2": {
    "{{ #flatten }}": {
      "{{ #each items }}": "{{ this }}"
    }
  },
  "items3": {
    "{{ #flatten }}": [ 1, 2, [ 3, 4 ], [ 5, 6, [ 7, 8 ] ] ]
  }
};

console.log(
  JSON.stringify(
    ST.transform(template, data),
    null, 2));

/*
Prints:

{
  "items1": [
    1,
    2,
    3,
    4,
    5,
    6,
    7
  ],
  "items2": [
    1,
    2,
    3,
    4,
    5,
    6,
    7
  ],
  "items3": [
    1,
    2,
    3,
    4,
    5,
    6,
    [
      7,
      8
    ]
  ]
}
 */

Template operator

My project works with large complicated transformations and so it's easier to split the template into smaller parts using partial templates that are then added together later on. I understood that you had already implemented this functionality using partial selections and then a transformation but I just could not wrap my head around it. I tried some variations but wasn't even able to make the example on your page work. Also I wanted to my application to be universal. To be able to make multiple different transformations without changing the JavaScript code - just the templates. And so instead of going through the templates, isolating partial templates' names and transforming them with actual partial templates I decided to include partial templates' logic into the st.js library itself.

And so I extended the library with partial template dictionary and added a function setTemplates which adds additional partial templates to the library. This function is called just as select or transform or transformWith function right on ST object.

Usage:

const ST = require('stjs');

// Accepts: JSON data, main JSON template with STJS syntax, partial JSON templates with STJS syntax
// Returns: JSON output based on given templates using data information
function transform(data, template, templates) {
  return ST.setTemplates(templates).transform(template, data);
}

Now added a partial template to the actual template is as easy as using template operator with name of the partial template as the expression part.

Usage:

const ST = require('stjs');

let data = {
  objects: [{
    name: 'Jakub',
    surname: 'Mifek',
    age: 23
  }, {
    name: 'Some',
    surname: 'Dummy',
    company: 'Some company'
  }, {
    name: 'Lojza',
    surname: 'Smolař',
    age: 50
  }]
};

let partial_template = {
  "{{ #each objects }}": {
    name: "{{ name }} {{ surname }}",
    company: "{{ #? company }}",
    role: [{
      "{{ #if surname == 'Mifek' }}": 'Developer'
    }, {
      "{{ #elseif surname == 'Smolař' }}": 'Punching Bag'
    }, {
      "{{ #else }}": undefined
    }],
    age: "{{ #? age }}"
  }
};

let template = {
  beforelet: "{{ #template partial_template }}",
  employees: {
    "{{ #let }}": [{
      company: 'Best company'
    }, "{{ #template partial_template }}"]
  },
  afterlet: "{{ #template partial_template }}"
};

console.log(
  JSON.stringify(
    ST.setTemplates({
      "partial_template": partial_template
    }).transform(template, data),
    null, 2));

/*
Prints:

{
  "beforelet": [
    {
      "name": "Jakub Mifek",
      "role": "Developer",
      "age": 23
    },
    {
      "name": "Some Dummy",
      "company": "Some company"
    },
    {
      "name": "Lojza Smolař",
      "role": "Punching Bag",
      "age": 50
    }
  ],
  "employees": [
    {
      "name": "Jakub Mifek",
      "company": "Best company",
      "role": "Developer",
      "age": 23
    },
    {
      "name": "Some Dummy",
      "company": "Best company"
    },
    {
      "name": "Lojza Smolař",
      "company": "Best company",
      "role": "Punching Bag",
      "age": 50
    }
  ],
  "afterlet": [
    {
      "name": "Jakub Mifek",
      "role": "Developer",
      "age": 23
    },
    {
      "name": "Some Dummy",
      "company": "Some company"
    },
    {
      "name": "Lojza Smolař",
      "role": "Punching Bag",
      "age": 50
    }
  ]
}
 */

I hope that you like my additions to your code and will consider including them into your project.

Best wishes,

Jakub Mifek

PS: I don't know if you know this but your stjs npm module still runs on old stjs version. You might consider updating it. :)

PPS: I am sorry for any mistake in my English.

## Ease of partial template use
```js
let data = {
  "items": [ [ 1, 2, 3 ],
                 [ 4, 5, 6 ], 7 ]
};
let template = {
  "items": {
    "{{#each items}}": "{{#template partial_template}}"
  }
};
let partialTemplate = {
  "sub-item": "{{this}}"
};
ST.setTemplates({
  "partial_template": partialTemplate
}).transform(template, data)
```
Results in:
```json
{
  "items": [
    {
      "sub-item": [
        1,
        2,
        3
      ]
    },
    {
      "sub-item": [
        4,
        5,
        6
      ]
    },
    {
      "sub-item": 7
    }
  ]
}
```

## Let statement memory clean-up
After the let statement ended, the memory wasn't cleaned which could result in conflict especially when using same partial templates at multiple locations.
If lasting of the variable in the memory was intended I would recommend declaring new function #var for that purpose.

## flatten function
I introduced new function to the TRANSFORMER functionality. Just as there are function each, merge or concat, I defined function #flatten. The flatten function is used for 'flattening' of given array.

```js
let data = {
  "items": [ [ 1, 2, 3 ],
                 [ 4, 5, 6 ], 7 ]
};
let template = {
  "items1": {
    "{{#flatten}}": "{{items}}"
  },
  "items2": {
    "{{#flatten}}": {
      "{{#each items}}": "{{this}}"
    }
  },
  "items3": {
    "{{#flatten}}": [1, 2, [3, 4], [5, 6, [7, 8]]]
  }
};
ST.transform(template, data)
```
Results in:
```json
{
  "items1": [
    1,
    2,
    3,
    4,
    5,
    6,
    7
  ],
  "items2": [
    1,
    2,
    3,
    4,
    5,
    6,
    7
  ],
  "items3": [
    1,
    2,
    3,
    4,
    5,
    6,
    [
      7,
      8
    ]
  ]
}
```
## Optional
Optional is a function that can be called on a key as follows:
```js
let data = { items: [1, 2, [3, 4], [5, 6, [7, 8] ] ] }
let template = {
  "{{#optional items}}": [{
    "{{#if items.length > 5}}": "{{items}}"
  }]
}
ST.transform(template, data)
```
This results in following JSON:
```json
{ }
```
This is because, the value attached to the key 'items' is empty. 
The functionality is meant to be same as #? operator just for more complicated situations. The key will not be used if the result is:
 - undefined
 - null
 - false
 - empty object
 - empty array
There was a but in memory cleanup in #each when #let was used. This fixes it.
@mihir83in
Copy link

+1

JakubMifek and others added 4 commits March 11, 2019 08:22
$this variable references current context within a loop cycle.
This saves the hassle of having nested let’s.

Also merge let/let* results with whatever was there before, instead of over-writing.
@JakubMifek
Copy link
Author

JakubMifek commented Apr 26, 2020

Hi, it seems that this project is somehow dead. I still use the project within my own projects and set up new repository for it. I rewrote the thing into typescript and used modular approach for template functions. You can find it here: https://github.com/JakubMifek/SelectTransform

There are huge differences in approach to the code but none in functionality. If you don't count multiple features that I added like optional async execution 🙂

This message is mainly for other developers interested in this project. However if @gliechtenstein ever reads this, I'd love to go through the project and some of my ideas with you.

Cheers.

PS: The new repository can be included to your projects using https://www.npmjs.com/package/selecttransform

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants