Opposite to "Inverted Selection" with Mustache - javascript

I'm curious as to whether Mustache has a syntax feature that is the opposite of the "Inverted Selection" syntax {{^foo}}.
Let's say we have the data:
{
people: [
{ name: "Alice" },
{ name: "Bob" },
{ name: "Mallory" }
]
}
And the template:
<h3>People</h3>
<ul>
{{#people}}
<li>{{name}</li>
{{/people}}
</ul>
If we wanted to add an item if there are no people, we just add something like:
{{^people}}<li>No people</li>{{/people}}
But in my case I don't want to show the <h3> and <ul> if there are no people.
A syntax like {{?people}} (representing the opposite / boolean negation of {{^people}}) would be perfect for this situation, but does one exist?
Another alternative would be to preprocess the data, adding a value
has_people = Boolean(people.length)
and using {{#has_people}}, but this seems counter-intuitive and against the logic-less (and I use that term lightly) paradigm.

Simplest solution seemed to be to add the functionality myself.
I've forked the Mustache.js repository with the changes required to introduce the {{? data }} selector. In English it could be read as 'When true, or there are items within data.'
Repository | Diff 1 | Diff 2

Related

How do I group my list by an attribute which can be null in SAP UI5?

Im trying to group a list of items from an entity called Services. I want to group them by the attribute group1_ID. The problem is that some Services dont belong to a group, in which case the value for group1_ID is Null.
Ive run it with generated mock data where every Service had values !== Null for group1_ID. In this case it worked fine with my original attempt.
What I had originally written was this:
<List id="_phaseOverviewList"
items="{
path:'/Services',
sorter: {path: 'group1_ID', group: true}}">
<StandardListItem id="_IDEGen_standardlistitem0" title="{name}"/>
</List>
I've also considered trying to sort it in the Controller.js for that View. I guess that would go in the direction of the following, but I'm not sure and I'm not sure how I should implement that.
oList.getBinding(„items“).sort(new sap.ui.model.Sorter(„group_ID“, false, true));
I had hoped that the Services would be grouped based on the group1_ID attribute, and that Services with a Null value for group1_ID would be grouped together in the list. However, it just displays the list, unsorted/ungrouped.
Thanks in advance for your help.
From the specs what you do should already work, the null values should come at end. Instead of saying group: true you can provide a function that returns the group for each element. Something like this:
items="{
path:'/Services',
sorter: {path: 'group1_ID', group: function(oContext){
let x = oContext.getProperty('group1_ID') || ''; //defaults to an empty string
return { key: x, title : x }}}}"
Have a look at https://gist.github.com/ikiw/bb2de7cd162bd88adf88 and for working examples. https://blogs.sap.com/2013/11/29/custom-sorter-and-grouper/

Working with numbers in forms

I'm building an app with Aurelia and really liking the framework so far, but I've stumbled upon an issue where I'm trying to display a list of checkboxes whose values are numbers (ID:s in reality) and Aurelia seems to convert them to strings and thus comparison fails.
I basically have something like:
export class MyVm {
constructor () {
this.items = [
{name: 'Foo', id: 0},
{name: 'Bar', id: 1},
{name: 'Baz', id: 2}
];
this.selectedItems = [0, 2];
}
}
And in my view:
<ul>
<li repeat.for="item of items">
<input type="checkbox" value.bind="item.id" checked.bind="selectedItems">
${item.name}
</li>
</ul>
For this to work I actually have to do this.selectedItems = ["0", "2"] which just leads to a bunch of other comparison problems in my own code. I also don't want to send the selected item as a string to the server later on when saving the data.
I've tried using a simple value converter that converts toString toView and parseInt fromView, but I can't run this converter on the array of selectedItems:
export class IntValueConverter {
toView (val) {
return val.toString();
}
fromView (val) {
return parseInt(val);
}
}
How would you go about solving this?
You almost have it. There's one small problem with this part:
<input type="checkbox" value="${item.id}" checked.bind="selectedItems">
The <input> element's value attribute coerces everything it's assigned to a string. Not only that, a string interpolation binding (eg ${...}) also coerces everything to a string. You need to preserve the numeric item id. Replace value="${item.id}" with model.bind="item.id" and it will work. No need for a converter.
Coincidentally I just pushed a set of exhaustive docs on checkbox, radio and select binding to the Aurelia binding repo. They haven't been published to the official Aurelia docs app yet but they should be there on Tuesday.
Also- if you see anything weird with items whose id is zero- there's a fix going out on Tuesday for that as well.
Finally, I know this is not your question, but for others that land here looking for binding numbers in forms, here's a couple basic examples using a custom element and custom attribute:
https://gist.run/?id=d9d8dd9df7be2dd2f59077bad3bfb399

Ng-repeat of keys in a nested object

I am trying to use ng-repeat to create list of checkboxes for the keys in a nested object.
My object loosk like:
$scope.kids = [
{
"name": "Will",
"age": 6,
"skills": {
"dancing": false,
"coloring": true
}
},{
"name": "Sally",
"age": 7,
"skills": {
"dancing": false,
"coloring": true,
"runnning": true
}
}
];
and I would like a unique list of the keys in the "skills" object with each skill listed only once, not once for each kid. (i.e. "dancing", "coloring", "running")
This was helpful, but I still can't get a unique list after trying the nested repeats
Here's my current attempt on JSFiddle
Thanks!
Modify Your HTML
I think you need to change your html, remove your ng-repeats with this
<span ng-repeat="kid in kids">
<span ng-repeat="(key,skill) in kid.skills">
<input type="checkbox" ng-modle="{{skill}}"> {{key}}
<br>
</span>
</span>
This should give you a list of boxes connected to the correct $scope and titles.
Here is a link to the modified jsfiddle
Edit
I've modified the code to display a list of all the kids and then within that list we have a list of all of the kids individual skills. Its basically about understanding how loops work.
If I got your question correctly, the best solution for you will be to build a filter that extracts the keys, sorts them and pull unique values. Then you can use it in a single repeat. I forked your jsfiddle and added the filter.
I chose to use lodash to quickly pull out the values, flatten, sort and remove dups. Lodash is a huge playground and you could make the filter even more shorthand.
Since you have a repeating access to this filter, I'd suggest you use memoize to avoid redundant evaluation of the filter. Once it is computed, it is good enough. The basic memoization uses a single key and will not be able to account for other parameters but you can override it and take all params into account and have an accurate and efficient memoization.
The filter is defined like so:
mymodule.filter('uniqueKeys', function(){
return function(input, keyPath){
return _.unique(_.sortBy(_.flatten( _.map(input,
function(item){return _.keys(_.get(item, keyPath));}))));
}
});
and the repeater would be:
<div ng-repeat="skill in kids | uniqueKeys:'skills'">
Note that I pass in the key to find your values, if you had hobbies field that'd you'd like to extract, you just have to write:
<div ng-repeat="skill in kids | uniqueKeys:'hobbies'">

Sorting with Angularjs OrderBy in special cases

I have some data which I'm attempting to sort in a table using Angular's orderBy filter. Here's the data I'm attempting to sort.
[
{
"name":"John Doe",
"room":"1M-103",
},
{
"name":"Joe Schmoe",
"room":"12M-353",
},
{
"name":"Bob Guy",
"room":"13M-546",
},
{
"name":"Another Person",
"room":"12M-403",
},
{
"name":"Fred No-name",
"room":"3M-204",
},
[
So sorting by name works just fine. Sorting by room, however, is another issue. I'm sorting it the exact same way. Here's what I would like it to look like after sorting:
1M-103
3M-204
12M-353
12M-403
13M-546
How it actually looks after the sorting is this:
12M-353
12M-403
13M-546
1M-103
3M-204
Anyone able to help? I know why it's sorting like that, because it's going digit by digit and sorting it correctly, but is there some way that someone has found to get some slightly better sorting?
You can create a custom sorter to do that.
<li ng-repeat="item in items | orderBy:mySorter">{{item}}</li>
$scope.mySorter = function (item) {
return Number(item.room.split('-')[0].replace('M', ''));
}
I also created a demo for you.
Updated
You can also use Regex:
$scope.mySorter = function (item) {
return Number(item.room.replace(/[^\d]/g, ''));
}
Demo
Actually sza's answer doesn't quite work, since it only takes into account the numbers before the letter 'M'. So everything that starts with 12M would be read as '12' and considered equal. All the 12M's would come after 11M's and before 13M's, but would then be displayed in a random order among other 12M's.
I worked up this code:
<li ng-repeat="item in items | orderBy:mySorter">{{item}}</li>
$scope.mySorter = function (item) {
return Number(item.residenceRoom.replace('M', '.').replace('-', ''));
}
It creates a decimal number based on the number before the M dot the number after the M. So 12M-353 would be '12.353' and 12M-403 would be '12.403' so they would be sorted correctly.
The only problem I see is if the room numbers aren't always 3 digits. For example a hypothetical room 1M-2 would be sorted as '1.2' while room 1M-103 would be '1.103' so a room 2 would be sorted after room 103. But you could fix this pretty easily by formatting all your rooms with 3 digits, eg. room 1M-002.

AngularJS - complex filtering based on categories etc

I've done some google-fu but all I can find about AngularJS filters is simple examples about simple filters (mostly on a single field's value).
What I'm after thoguh is somewhat more complex, and I kinda look for help on how to tackle my situation.
Imagine you have an array of the following JSON objects:
{
"id":"1",
"title":"Title",
"categories":[
{"id":"14","name":"DIY"}
],
"topics":[
{"id":"12","name":"Junk Food"}
]
},
{
"id":"2",
"title":"Title 2",
"categories":[
{"id":"4","name":"Test"},
{"id":"14","name":"DIY"},
],
"topics":[
{"id":"2","name":"Food"}
]
}
[...]
so basically each object can have ANY number of "categories" and / or "topics".
Now, my goal is to create a frontend interface that allows me to cumulatively apply various kinds of filters to those JSON objects.
For example, I'd like to say: show only the entries that have category.id = 14 AND topic.id = 2 [etc] and still support deep-linking for the filtered results.
So here's where I'm stuck:
1) what's the best way to use routes for this (ie how would you structure the URLs to support ANY number of filter (based on different values)
2) how should i keep track of the filters added? (ie, how many and which filters have been selected by the user)
Looking at the documentation for the AngularJS filters I'll obviously use the 2nd example for the filtering parameter:
Object: A pattern object can be used to filter specific properties on objects contained by array. For example {name:"M", phone:"1"} predicate will return an array of items which have property name containing "M" and property phone containing "1". A special property name $ can be used (as in {$:"text"}) to accept a match against any property of the object. That's equivalent to the simple substring match with a string as described above.
But I'm not so sure on how to make sure i'm checking the right field (ie topic.id for topics vs category.id for categories)...
Simply put, I'd love to see an example for such a less-trivial filtering scenario.
I think you need something like this instead. See his 'other simple alternative'. I do complex filtering in a service that's injected into my controller, and expose the filtered list on my $scope to the View. I only use Angular filters for relatively simple tasks.
Re: the question about how to expose this on the URL, you'll need some way of representing those filters as strings, and can use $location and $routeParams to populate them into your controller.
This can work if you write a custom filter:
var module = angular.module('app', []);
module.filter("property", ["$filter", function($filter){
var parseString = function(input){
return input.split(".");
}
function getValue(element, propertyArray) {
var value = element;
angular.forEach(propertyArray, function(property) {
value = value[property];
});
return value;
}
return function (array, propertyString, target) {
var properties = parseString(propertyString);
return $filter('filter')(array, function(item){
return getValue(item, properties) == target;
});
}
}]);
HTML part can look like this:
<ul>
<li ng-repeat="data in items | property:'categories.id':<id_of_a_category_we_want>">
{{ data }}
</li>
</ul>
Credit: OnOFF-Switch blog

Categories

Resources