sorting values of a hash object in ngRepeat - angularJS - object

I have an object like this:
$scope.phones = new Object();
$scope.phones['id1'] = {
"name":"Phone Name1",
"dateReleased":"2012-1-09 15:48:24"
};
$scope.phones['id2'] = {
"name": "Phone Name2",
"dateReleased":"2012-3-12 15:32:11"
};
$scope.phones['id3'] = {
"name": "Phone Name3",
"dateReleased":"2012-2-10 13:53:32"
};
I'm displaying this using ngRepeat. I'm not able to order by dateReleased. Also, ordering in reverse isn't working. My ngRepeat looks this:
<li ng-repeat="phone in phones | orderBy:dateReleased:true">
<p>{{phone.name}}</p>
<p>{{phone.dateReleased}}</p>
</li>

While ngRepeat can iterate a hash object, like $scope.phones in your example, the built-in orderBy filter will not work. I believe this is due to the way objects are stored. As other's have noted, you need to convert the hash to an array. While you can do this using the methods suggested above, I prefer to do it using a custom filter. This gives me the benefit of not having to alter my hash directly, and also let's me reuse the filter with other hashes.
yourApp.filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (a[field] > b[field] ? 1 : -1);
});
if(reverse) filtered.reverse();
return filtered;
};
});
This filter converts the object into a standard array and sorts it by the field you specify. You can use the orderObjectBy filter exactly like orderBy, including a boolean value after the field name to specify whether the order should be reversed. In other words, false is ascending, true is descending.
<li ng-repeat="phone in phones | orderObjectBy:'dateReleased':true">
<p>{{phone.name}}</p>
<p>{{phone.dateReleased}}</p>
</li>
I've got a post on my blog regarding this topic.

If you check the documentation it says that the expression in orderBy can be a function, a string or an Array. An therefore you need dateReleased to be a string: 'dateReleased'
Also you need your phones Object be an actual Array.
Try:
$scope.phones = [{
"name":"Phone Name1",
"dateReleased":"2012-1-09 15:48:24"
},{
"name": "Phone Name2",
"dateReleased":"2012-3-12 15:32:11"
},{
"name": "Phone Name3",
"dateReleased":"2012-2-10 13:53:32"
}];
<li ng-repeat="phone in phones | orderBy:'dateReleased':true">
<p>{{phone.name}}</p>
<p>{{phone.dateReleased}}</p>
</li>

Both of the other answers get you part way there, but not all the way...
You'll need to create a function on your scope that converts the object to an array like so:
$scope.phonesArray = function() {
var result = [];
angular.forEach($scope.phones, function(phone, id) {
result.push(phone);
});
return result;
};
Then you'd call that instead of your object in your ngRepeat:
<li ng-repeat="phone in phonesArray() | orderBy:'dateReleased':true">
<p>{{phone.name}}</p>
<p>{{phone.dateReleased}}</p>
</li>
Also: Notice that 'dateReleased' is a string, so it knows to $eval that string off of the current item, otherwise it will check the parent $scope.dateReleased, which doesn't exist.
Here is a plunk for what I think you're trying to do
EDIT: You can also "convert" the object to an array and store it on the $scope, if you're worried about the function to do so being too "expensive", but that shouldn't be an issue, as you're developing a clientside app for one user, and not a server application for many users, meaning you have a little wiggle room for "expensive". (Which it won't be anyway)

First of all, you need to understand that ng:filter and ng:orderBy work with Arrays (an ordered collection of items), but you're trying to use them on Object (_un_ordered collection of items). One possible approach is to collect all the objects into an array, then proceed with ng-repeat on it instead. Like this:
<ul ng-init="phones = [
{name:'Phone Name 1', dateReleased:'2011-1-09 15:48:24'}
, {name:'Phone Name 2', dateReleased:'2012-3-12 15:32:11'}
, {name:'Phone Name 3', dateReleased:'2012-2-10 13:53:32'}];
pred = '-dateReleased';" >
<li ng-repeat="phone in phones | orderBy:pred">
<p>{{phone.name}}</p>
<p>{{phone.dateReleased}}</p>
</li>
</ul>

Related

can I have <br> linebreaks in title of an existing html table?

(This is loading Tabulator from HTML)
I don't seem to be able to point the column definitions to existing titles if those titles contain a line break.
i.e. this table>tr>th will not work
<th >Primary<br/>Permission List?</th>
with
"columns": [
{
"title": "Primary<br/>Permission List?"
},
...
Is there any way around this? Aliases? I assume the field attribute doesn't help here. Formatting differently in the columns options? Can I modify the <th> HTML after the table is linked?
Not a huge deal if not possible, just checking.
Version: tabulator-tables#5.4.2
You could use a titleFormatter to display pretty much whatever you like in the title cell.
eg, in the column definition:
"columns": [
...,
{
title: "My<br>Multi<br>Line<br>Title",
titleFormatter: (cell) => this.myTitleFormatter(cell),
...,
}
and then the formatter function:
private myTitleFormatter(cell) {
const splitChars = "<br>";
const cv = cell?.getValue() ?? "";
const splits = cv.split(splitChars);
if (splits.length > 1) {
const el = document.createElement('span');
el.appendChild(document.createTextNode(splits[0]));
splits.slice(1).forEach(s => {
el.appendChild(document.createElement('br'));
el.appendChild(document.createTextNode(s));
});
return el;
} else {
return cv
}
}
The <br> is not actually HTML, just a marker/split point so you could use anything else like, say, '#$#':
"My#$#Long#$#Title"
and use that for the splitChars in the formatter.

Why some of the variables are not changed in the nested tag, in RiotJs?

I have a simple nested tag:
<nested-tag>
<p>myTitle: {myTitle}</p>
<p>{myKeyword}</p>
this.myTitle = opts.title;
this.myKeyword = opts.keyword;
</nested-tag>
You can see I assign the opts.title and keyword to two new variable myTitle and myKeyword.
Then I use it in a loop of a parent tag:
<my-tag>
<input type="text" onkeyup={search} value={keyword} />
<ul>
<li each={items}>
<nested-tag title={title} keyword={parent.keyword}></nested-tag>
</li>
</ul>
this.keyword = ""
var initItems = [{ title: "aaaa"}, { title: "bbbb"} ]
this.items = initItems
this.search = function(event) {
this.keyword = event.target.value;
this.items = initItems.filter((item) => item.title.indexOf(this.keyword) >=0 );
}
</my-tag>
You can see I passed the parent.keyword to nested-tag as keyword variable.
When I input something to the text input, the keyword will be changed, so the <nested-tag> will be recreated with the new parent.keyword.
But it's not, the {myKeyword} of nested-tag is always empty. I have to rewrite it with directly opts.keyword invocation:
<nested-tag>
<p>opts.title</p>
<p>opts.keyword</p>
</nested-tag>
And it's working well now.
I'm not sure why and how to fix it? Do I have to always use opts.xxx in the nested tags?
A live demo is here:
http://jsfiddle.net/3jsay5dq/10/
you can type something to the text input to see the result
The javascript in your component nested-tag gets run when instantiating the component. So, when the component is getting generated, the myTitle and myKeyword will be initialized with whatever opts are passed in. But, on update, the myTitle and myKeyword are still pointing to the values set during instantiation. The cleanest way to go about it is to use opts[key] as they will always reflect what is being passed to the component. If you insist on using your own local properties, then you could modify your component like this:
<nested-tag>
<p>myTitle: {myTitle}</p>
<p>{myKeyword}</p>
// this will run every time there is an update either internally or from a passed opts
this.on('update', () => {
this.myTitle = this.opts.title;
this.myKeyword = this.opts.keyword;
})
// this will only run once during instantiation
this.myTitle = opts.title;
this.myKeyword = opts.keyword;
/*
// could be refactored to
this.setMyProps = () => {
this.myTitle = this.opts.title;
this.myKeyword = this.opts.keyword;
}
// bind it to update function
this.on('update', this.setMyProps)
// run once for instantiation
this.setMyProps()
*/
</nested-tag>

Creating alerts from changes at the item level in Netsuite

I am trying to incorporate a check at the item line level when creating an invoice. Basically if they are adding an item within a certain category (custitem8) i need an alert to pop up for the sales rep.
Not sure if this should be using fieldchanged or validateline.
Sorry Im not really a programmer and am learning on the job mostly by trial and error. Thanks for your help.
function ValidateLine(type)
{
if (nlapiGetCurrentLineItemValue('item', 'custitem8') = 'Order in Only - Not For Trade Guide')
{
alert("Order In Only, Please contact Purchasing");
}
return true;
}
The suggested code will not work, instead of using nlapiGetLineItemValue use nlapiGetCurrentLineItemValue.
the code should look like this.
postSourcing(sublistId, fieldId) {
if(sublistId == "item" && fieldId == "item") {
var itemId = nlapiGetCurrentLineItemValue(sublistId, fieldId);
var category = nlapiLookupField("item", itemId, "custitem8");
if(category == "Order in Only - Not For Trade Guide") {
alert("Order In Only, Please contact Purchasing");
}
}
}
I'm assuming you just need an alert when the user selects a line Item? If so, I would suggest using postSourcing(sublistId, fieldId) (though using validateLine(sublistId) works just fine).
As for the actual function content, I'm assuming (based on the field ID) "custitem8" is a field on the Item record. If so, you will have to load the field from the Item record first.
Based on my understanding of your post, I would go about it like this:
postSourcing(sublistId, fieldId) {
if(sublistId == "item" && fieldId == "item") {
var itemId = nlapiGetLineItemValue("item", "item");
var category = nlapiLookupField("item", itemId, "custitem8");
if(category == "Order in Only - Not For Trade Guide") {
alert("Order In Only, Please contact Purchasing");
}
}
}
And just a note, I don't really know the data type of the "custitem8" field, so I'm just assuming it's a free-form text field.

phalcon framework -> edit model::find(); results in an foreach loop

As http://docs.phalconphp.com/en/latest/reference/models.html#understanding-records-to-objects says, you can edit the objects once its loaded in the memory.
$settingCategories = SettingCategory::find();
foreach($settingCategories as $settingCategory){
if($settingCategory->type == "2"){
$settingCategory->type = "asd";
$settingCategory->intersection = "asd";
}else{
$settingCategory->type = "blaa";
$settingCategory->intersection = "blaa";
}
$settingCategory->type = "test";
}
$this->view->setVar("settingCategories",$settingCategories);
type is still its default value when I loop through it with volt:
{% for settingCategory in settingCategories %}
<div class="tab-content">
<h4>{{ settingCategory.name }}</h4>
<h4>{{ settingCategory.type }}</h4> --> still (int) integer!?
<h4>{{ settingCategory.intersection }}</h4> --> undefined!?
</div>
{% endfor %}
When you are modifying a variable inside a foreach, you are modifying a "temporary variable". What it means is that since it is only a copy of the real variable, when you change it, the real value inside the array isn't changed. Now, on to what you could do to solve this:
Setters/Getters
I personally prefer this one. If what you want to do is data transformation (I.E. you change the value of a field from one thing to another, and you want to use the new value in your code everywhere), I would use setters and getters. Here is an example:
// This is inside your model
protected $type;
public function getType()
{
if ($this->type === 2) {
return "asd";
} else {
return $this->type;
}
}
public function setType($type)
{
if ($type === 2) {
$this->type = "asd";
} else {
$this->type = 1; // or $type, or anything really :)
}
}
Of course, in your code, you'll have to change $category->type to $category->getType() and $category->setType($type), based on whether you are reading the value or assigning something to it.
The Quick and Dirty Way
Well, if your use case is different, you can use your current code block with a simple modification. Change your foreach to foreach($settingCategories as &$settingCategory). The ampersand makes the variable be passed into the block as a reference (I.E. it is not a copy like your current case). That means changing it will change the real value.

mustache.js date formatting

I have started using mustache.js and so far I am very impressed. Although two things puzzle me. The first leads on to the second so bear with me.
My JSON
{"goalsCollection": [
{
"Id": "d5dce10e-513c-449d-8e34-8fe771fa464a",
"Description": "Multum",
"TargetAmount": 2935.9,
"TargetDate": "/Date(1558998000000)/"
},
{
"Id": "eac65501-21f5-f831-fb07-dcfead50d1d9",
"Description": "quad nomen",
"TargetAmount": 6976.12,
"TargetDate": "/Date(1606953600000)/"
}
]};
My handling function
function renderInvestmentGoals(collection) {
var tpl = '{{#goalsCollection}}<tr><td>{{Description}}</td><td>{{TargetAmount}}</td><td>{{TargetDate}}</td></tr>{{/goalsCollection}}';
$('#tblGoals tbody').html('').html(Mustache.to_html(tpl, collection));
}
Q1
As you can see my 'TargetDate' needs parsing but I am unsure of how to do that within my current function.
Q2
Say I wanted to perform some function or formatting on one or more of my objects before rendering, what is the best way of doing it?
You can use "Lambdas" from mustache(5)
"TargetDate": "/Date(1606953600000)/",
"FormatDate": function() {
return function(rawDate) {
return rawDate.toString();
}
}, ...
Then in the markup:
<td>
{{#FormatDate}}
{{TargetDate}}
{{/FormatDate}}
</td>
From the link:
When the value is a callable object, such as a function or lambda, the object will be invoked and passed the block of text. The text passed is the literal block, unrendered.
I have created a small extension for Mustache.js which enables the use of formatters inside of expressions, like {{expression | formatter}}
You would anyway need to create a function that parses your date value like this:
Mustache.Formatters = {
date: function( str) {
var dt = new Date( parseInt( str.substr(6, str.length-8), 10));
return (dt.getDate() + "/" + (dt.getMonth() + 1) + "/" + dt.getFullYear());
}
};
And then just add the formatter to your expressions:
{{TargetDate | date}}
You can grab the code from here: http://jvitela.github.io/mustache-wax/
It's a long time ago but got on this looking for exactly the same. Mustachejs (now) allows you to call functions of the passed data and not only that; in the function the value of this is whatever value is true in a section.
If my template is like this:
{{#names}}
<p>Name is:{{name}}</p>
<!-- Comment will be removed by compileTemplates.sh
#lastLogin is an if statement if lastLogin it'll do this
^lastLogin will execute if there is not lastLogin
-->
{{#lastLogin}}
<!--
formatLogin is a method to format last Login
the function has to be part of the data sent
to the template
-->
<p>Last Login:{{formatLogin}}</p>
{{/lastLogin}}
{{^lastLogin}}
not logged in yet
{{/lastLogin}}
{{#name}}
passing name to it now:{{formatLogin}}
{{/name}}
{{/names}}
And Data like this:
var data={
names:[
{name:"Willy",lastLogin:new Date()}
],
formatLogin:function(){
//this is the lastDate used or name based on the block
//{{#name}}{{formatLogin}}{{/name}}:this is name
//{{#lastLogin}}{{formatLogin}}{{/lastLogin}}:this is lastLogin
if(!/Date\]$/.test(Object.prototype.toString.call(this))){
return "Invalid Date:"+this;
}
return this.getFullYear()
+"-"+this.getMonth()+1
+"-"+this.getDate();
}
};
var output = Mustache.render(templates.test, data);
console.log(output);
You can get the timestamp using simple String methods:
goalsCollection.targetDate = goalsCollection.targetDate.substring(6,18);
Of course, this depends on your timestamp being the same length each time. Another option is:
goalsCollection.targetDate =
goalsCollection.targetDate.substring(6, goalsCollection.targetDate.length - 1);
These techniques aren't specific to Mustache and can be used to manipulate data for any library. See the Mozilla Developer Center Documentation on substring for more details.
To declare a function within a json you can always do this.
var json = '{"RESULTS": true, "count": 1, "targetdate" : "/Date(1606953600000)/"}'
var obj = JSON.parse(json);
obj.newFunc = function (x) {
return x;
}
//OUTPUT
alert(obj.newFunc(123));
Working example of a 'lambda' function for parsing an ISO-8601 date and formatting as UTC:
var data = [
{
"name": "Start",
"date": "2020-04-11T00:32:00.000-04:00"
},
{
"name": "End",
"date": "2022-04-11T00:32:00.000-04:00"
},
]
var template = `
{{#items}}
<h1>{{name}}</h1>
{{#dateFormat}}
{{date}}
{{/dateFormat}}
{{/items}}
`;
var html = Mustache.render(template, {
items: data,
dateFormat: function () {
return function (timestamp, render) {
return new Date(render(timestamp).trim()).toUTCString();
};
}
});
document.getElementById("main").innerHTML = html;
<script src="https://unpkg.com/mustache#4.2.0/mustache.min.js"></script>
<div id="main"></div>
If you want fancier date formatting you could use for example something like:
new Date().toLocaleDateString('en-GB', {
day : 'numeric',
month : 'short',
year : 'numeric', hour: 'numeric', minute: 'numeric'
})
// outputs '14 Apr 2022, 11:11'
I've been using Mustache for my projects as well, due to its ability to be shared across client/server. What I ended up doing was formatting all values (dates, currency) to strings server-side, so I don't have to rely on helper Javascript functions. This may not work well for you though, if you're doing logic against these values client-side.
You might also want to look into using handlebars.js, which is essentially Mustache, but with extensions that may help with client-side formatting (and more). The loss here is that you will probably not be able to find a server-side implementation of handlebars, if that matters to you.

Resources