Angular JS, bind scope property to function - angularjs

I'm new to Angular JS. I am creating an app that stores data in the web storage. WHen an enquiry form is submitted, it gets the current arrays from the web storage, strigifies the new form data, adds it to an array, and posts it back to web storage. Works great and all gets saved correctly.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
}
function getStoredData(storeName) {
// get or create enquiry store
var storedData = (typeof localStorage.getItem(storeName) !== 'undefined' && localStorage.getItem(storeName)) || "[]";
// parse store into object
storedData = JSON.parse(storedData);
return storedData;
}
function addStoredData(data, storeName) {
var storedData = getStoredData(storeName);
var count = storedData.length;
// form data into next submission slot
storedData[count] = data;
// turn back into JSON
storedData = JSON.stringify(storedData);
// slap it back in web storage
localStorage.setItem(storeName, storedData);
}
On my HTML page I have this;
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Date</th>
</tr>
<tr ng-repeat="e in enquiries">
<td>{{ e.firstName }}</td>
<td>{{ e.lastName }}</td>
<td>{{ e.date }}</td>
</tr>
</table>
So I want to be able to do this...
// list of stored enquiries
$scope.enquiries = function () {
return getStoredData(ENQUIRY_STORE);
}
But it doesn't bind and there are no rows. If I do this...
$scope.enquiries = getStoredData(ENQUIRY_STORE);
it works, but then I have to keep setting it when a new enquiry is submitted. Maybe Anulgar doesn't let you have use functions to return data for binding, but I thought it did.
Any help much appreciated. Thanks.

In the first way ($scope.enquiries = function() ...) it doesn't bind because you are making an ngRepeat over a function (it doesn't throw an error because a function has actually a length).
However, even if you did <tr ng-repeat="e in enquiries()"> you would have a infinite digest error because enquiries() is returning a different object each time and angular is waiting to have the same result twice to stop the digests cycle.
The best way would be:
$scope.submit = function() {
...
setEnquiries();
}
...
function setEnquiries() {
$scope.enquiries = getStoredData(ENQUIRY_STORE);
}
And keep looping through enquiries:
<tr ng-repeat="e in enquiries">

$scope.selectedENQUIRY_STORE = null;
$scope.getStoredData= function (e) {
$scope.selectedENQUIRY_STORE = e;
};
it binds when you clicked on that enquiry form on name

You simply have to retrieve the data again, upon submitting, so move your $scope.enquiries() function into your $scope.submit() function.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
// FIRE UP THE FUNCTION
$scope.enquiries();
}
Note that you should put getStoredData() and other data related function into an angular service.
Luckily, there's already a good module for dealing with LocalStorage operations :
https://github.com/tymondesigns/angular-locker

Related

ServiceNow spUtil

I'm trying to make a slight improvement to an existing widget that our team created, but can't seem to get it to work correctly. We have a widget that does a RowCount of tasks and groups them by state. I want the RowCount to auto update once a task is complete without having the user press the refresh button. I've read some documentation on $rootscope, $broadcast, and $on, but can't seem to get it to work.
Below is snippet of our HTML:
<table class="table table-sm table-responsive">
<tbody>
<tr class="h3">
<td colspan=2>Complete</td>
</tr>
<tr class="h2 bg-success" ng-repeat="x in data.values track by $index">
<td><span class="glyphicon glyphicon-check"></span></td>
<td>{{x.completedCount}}</td>
</tr>
</tbody>
</table>
A snippet of our Server Script:
var values = [];
var _completedCount;
var gsCompleted = new GlideRecordSecure('sn_hr_core_task');
//CLOSED COMPLETE, CLOSED INCOMPLETE,
gsCompleted.addQuery('state', 'IN', '3,4,7');
gsCompleted.addQuery('assigned_to', gs.getUserID());
gsCompleted.addQuery("parent.state", 'NOT IN', '1,800,900');
gsCompleted.query();
if(gsCompleted){
_completedCount = gsCompleted.getRowCount();
}
else{
_completedCount = 0;
}
values.push(
{
completedCount: _completedCount
});
data.values = values;
How do I get this widget to auto update the Completed row count without refreshing the page? I've been playing around with spUtil recordWatch, but cannot get it to work correctly:
function($scope, $sce, spUtil) {
var c = this;
c.data.loading = true;
//After page initially loads re-call server script to load data
c.server.get({
action: 'retrieve_data'
}).then(function(response) {
c.data.loading = false;
console.log('Response');
console.log(response);
c.data.values = response.data.values;
spUtil.recordWatch($scope, 'sn_hr_core_task', "", function(name,data) {
spUtil.update($scope);
})
});
}
Take a look at the widget Simple List, it has an example of one that may help a bit.
You should be able to change your recordWatch to this
var filter = "stateIN3,4,7^parent.stateNOT IN1,800,900^assigned_to=" + window.NOW.user_id;
spUtil.recordWatch($scope, 'sn_hr_core_task', filter);
You generally won't need a callback function unless there is some specific action you're triggering.

Grails GSP Loop through an index and do somthing with selected lines

In an Index-gsp, I want to be able to select an arbitrary number of lines and then by clicking a link send all those lines to a controller for processing e.g. creating new objects of a different kind.
I've no idea how selection can be done or how to collect these selected lines in a GSP. Maybe I should use a checkbox on each line if that's possible?
It's a list of products which is displayed using a modified index.gsp.
Each product-line has a checkbox in front.
What I want is to make a list of the products that are checked an then transmit this list to a controller.
a part of this index.gsp:
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
<li><g:link class="create" action="createOffer"><g:message code="default.new.label" args="[entityName]" params="toOffer" /></g:link></li>
</ul>
</div>
<div id="list-prodBuffer" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<table>
<thead>
<tr>
<td> Välj</td>
<td> ID</td>
</tr>
</thead>
<tbody>
<g:each in="${prodBufferList}" status="i" var="prodBuffer">
<tr class="${ (i % 2) == 0 ? 'even': 'odd'}">
<td><g:checkBox name="toOffer" value="${prodBuffer.id}" checked="false" /></td>
<td>${prodBuffer.id}</td>
So this not an ordinary form, just a list where I want to use a link to transmit it to the controller.
I'm a beginner and have no idea how to do it.
You can collect all necessary data from page using javascript, and then send all data to your controller for processing.
There are a lot of ways to do it.
For example send via JQuery:
<script>
//some code
var items = [1,2,3];
//some code
$('#add-location').click(function () {
$.ajax({
type: "POST",
url: "${g.createLink(controller:'myController', action: 'myControllerMethod')}",
data: {items: items},
success: function (data) {
console.log(data)
}
});
});
</script>
I will answer this but have to slow down since it feels like i am beginning to write your project:
In gsp you will need to have a hidden field followed by a check box amongst data you are trying to capture, checkbox should contain all the data elements required to build your output.
<g:hiddenField name="userSelection" value=""/>
<g:checkBox name="myCheckBox" id='myCheckBox' value="${instance.id}"
data-field1="${instance.field1}" data-field1="${instance.field1}"
checked="${instance.userSelected?.contains(instance.id)?true:false}" />
In the java script segment of the page you will need to add the following
This will then auto select selection and add to javascript array
// Customized collection of elements used by both selection and search form
$.fn.serializeObject = function() {
if ($("[name='myCheckBox']:checked").size()>0) {
var data=[]
$("[name='myCheckBox']:checked").each(function() {
var field1=$(this).data('field1');
var field2=$(this).data('field2');
data.push({id: this.value, field1:field1, field2:field2 });
});
return data
}
};
Most importantly will your data sit across many different gsp listing pages if so you will need to hack pagination:
//Modify pagination now to capture
$(".pagination a").click(function() {
var currentUrl=$(this).attr('href');
var parsedUrl=$(this).attr('href', currentUrl.replace(/\&userSelection=.*&/, '&').replace(/\&userSelection=\&/, '&'));
var newUrl=parsedUrl.attr('href') + '&userSelection=' + encodeURIComponent($('#userSelection').val());
window.location.href=newUrl
return false;
});
Then in the controller parse the JSON form field and make it into what you want when posted
def u=[]
def m=[:]
if (params.userSelection) {
def item=JSON.parse(params.userSelection)
item?.each {JSONObject i->
// When field1 is null in JSON set it as null properly
if (JSONObject.NULL.equals(i.field1)) {
i.field1=null
}
if (resultsGroup) {
if (!resultsGroup.contains(i.id as Long)) {
u << i
}
} else {
u << i
}
}
m.userSelected=item?.collect{it.id as Long}
m.results=u
}
return m

loop through firebase database object and ng-repeat the result

I am ng-repeating the objects stored in my $firebaseArrayobject.
mycontroller:
var projectquery = DatabaseRef.ref("projects").orderByKey().equalTo("-KUTPrs6ARZXjbjtNr7O");
$scope.projectslist = $firebaseArray(projectquery);
html view with ng-repeat:
<tr ng-repeat="obj in projectslist">
<td>{{ obj.field1}}</td>
<td>{{ obj.field2 }}</td>
<td>{{ obj.field3 }}</td>
<td>{{ obj.field4 }}</td>
</tr>
this is displaying only one project because I am passing in the project key manually to equalTo and thus filtering to show one.
From the other hand, I am getting a set of project keys from the below function by doing a forEach on another database where I get my project keys:
var keyquery = DatabaseRef.ref('/users/' + userId).orderByKey().once("value")
.then(function onSuccess(snapshot) {
snapshot.child("savedprojects").forEach(function(childSnapshot) {
var userprojects = childSnapshot.val();
console.log("my list is :", userprojects );
});
});
the output of the console is like:
my list is : -KUTLAZENGlxtzCtEfaZ
my list is : -KUTLM8r_kmVSTaxzLW5
my list is : -KUTPrs6ARZXjbjtNr7O
Question:
how is it possible to pass on the above list of project keys obtained from forEach into the first function which shows the projects and filters them by key?
Could somebody please give a relevant answer and help me solve this, without referring to other answers which are not directly related to my question?
thanks in advance!
edit:
my DatabaseRef refers to this factory, which is initializing the firebase database:
app.factory("DatabaseRef", function() {
return firebase.database();
});
update:
After implementing Gourav's answer, my console shows undefined in place of the project IDs:
as you can see, the user has six projects under his profile, but all of them are empty and undefined.
update 2:
here is console output after Gourav's edit:
and the error for ng-repeat:
I don't know much about DatabaseRef but this might help.
$scope.projectslist = [];
DatabaseRef.ref('/users/' + userId).orderByKey().once("value")
.then(function onSuccess(snapshot) {
snapshot.child("savedprojects").forEach(function (childSnapshot) {
var userprojects = childSnapshot.val();
console.log("my list is :", userprojects);
var projectquery = DatabaseRef.ref("projects").orderByKey().equalTo(userprojects);
$scope.projectslist.push(($firebaseArray(projectquery))[0]);
});
});

Handling data streaming using ng-repeat

I got the following data which comes out of a EventSource interface. I'm using this data to build a simple table (i have specified the table structure below) using angular ng-repeat. The issue i have is since the data changes rapidly i.e. server would be sending the changes continuously for the requested symbol and then it would keep upon updating the values. I want to add a row when data's are requested for a symbol and then should update the same row for the new values. This is pretty easy and is working.
But the real challenge is (atleast for me) when i request another symbol i want that to be added to a new second row and from now on it should update the values in both rows. It goes on as we request new symbols. With the following data structure is this possible, if so please explain.
Data Structure:
data = [
AMZN: {
name: Amazon,
symbol: AMZN,
openPrice: $100,
latestPrice: $200,
percentage: 0.1
},
FB: {
name: Facebook,
symbol: FB,
openPrice: $150,
latestPrice: $250,
percentage: 0.2
}
]
Table Structure:
<table>
<thead>
<tr>
<th>Session ID</th>
<th>Company Name</th>
<th>Symbol</th>
<th>Open Price</th>
<th>Latest Price</th>
<th>Percentage</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="stockVal in data">
<td>[Empty]</td>
<td>{{stockVal.companyName}}</td>
<td>{{stockVal.companySymbol}}</td>
<td>{{stockVal.openPrice}}</td>
<td>{{stockVal.latestPrice}}</td>
<td>{{stockVal.percentage}}</td>
</tr>
</tbody>
</table>
What happens now is when i do a request for AMZN the tables populates the values pertaining to Amazon and then if i request for FB it replace the values in the same row... i.e. both FB and AMZN gets replaces in the same row. Bottom line is i want FB values to be populated in new row.
Controller code:
All it does is making a request for the end point with window.EventSource and grab the data and attach it to the scope object. To avoid populating n number of rows the data array is flushed each time like making the length to 0.
Adding controller code below,
$scope.generateData = _generateData;
function generateData() {
endPoint = '/getData'
$scope.symbols = window.document.getElementById('symbols').value;
var eventSource = new window.EventSource(endPoint + '?&s=' + $scope.symbols);
eventSource.onmessage = function(e) {
var data = null;
try {
data = JSON.parse(e.data);
} catch (err){
}
if(data) {
$scope.$apply(function () {
$scope.companyName = data.name;
$scope.companySymbol = data.symbol;
$scope.openPrice = data.openPrice;
$scope.latestPrice = data.latestPrice;
$scope.percentage = data.percentage;
});
var specData = {
[$scope.companySymbol]: {
name: $scope.companyName,
symbol: $scope.companySymbol,
openPrice: $scope.openPrice,
latestPrice: $scope.latestPrice,
percentage: $scope.percentage
}
}
$scope.data[0] = specData[$scope.companySymbol];
}
}
};

angular js ng repeat asynchronously

I want to show a list of elements on view asynchronously.
For example i have a method which returns promise and result will look like this.
var result = {
books: [
{name: 'The Kite Runner', author: 'Khaled Hosseini'},
{name: 'The Book Thief', author: 'Markus Zusak'},
{name: 'A Thousand Splendid Suns', author: 'Khaled Hosseini'},
]
}
Here is a method:
function getBooks(userId) {
return BookProduct.getBooksByUser.query({ id: userId }).$promise;
};
And then i have a method which invokes getBooks.
$scope.showBooks = function(userId) {
//some users id
var users_id = [1,2,3,4,5];
$scope.tables = [];
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
}
Thereafter i want to show it to user on a view using angular ng-repeat.
<div data-ng-repeat="table in tables">
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="book in table.books">
<td>{{ name }}</td>
<td>{{ author }}</td>
</tr>
</tbody>
</table>
</div>
It works good but ng-repeat will work when function $scope.showBooks is executed and $scope.tables is already full. I am looking for more nice solution. I want to load books for user piecemeal on each iteration when method getBooks() invokes.
So in this part for example when the first iteration is processing and i get result of 2 elements, then i pass them to the view and user can see the first part. Next iteration i get for example other 3 elements, so i add them to previous result and pass to the view, so user can see 5 elements now, and so on. I know it will be almost insensibly for user but if my server responds slowly it will help me to be more user friendly.
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
I am not asking you HOW to implement it. I just want to know is it possible or not and where should i dig to know more, cause i tried some googling but without success.
Angular has a cycle in which it performs all scope functionality and after each cycle of "watches" it renders changes to the view. By default, Angular performs this automatically - for example simple change of variable in scope also rerenders view. But in some cases you may want to intercept because your operation didn't trigger the rerender.
For these cases, $apply and $digest are useful - more in documentation https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

Resources