The CreatorCon Call for Content is officially open! Get started here.

How to create multiple unique <sn-record-picker> objects within an ng-repeat?

jeremy_wirth
Tera Contributor

I am trying to create multiple <sn-record-picker> objects using an ng-repeat, however it seems that the typical way to uniquely track certain fields within your ng-repeat using ng-model and the $index to specify which object inside the array you are ng-repeat'ing through is not supported.

find_real_file.png

You are just left with the 'field' attribute, which is a JavaScript object consisting of "displayValue", "value" and "name".

Source: https://community.servicenow.com/community?id=community_blog&sys_id=54bde6a9dbd0dbc01dcaf3231f96193f

I have no trouble using the <sn-record-picker> as a standalone object, because that field object can be set to a controller in the client script as shown in the link above. My troubles arise when I need to access a certain <sn-record-picker> object uniquely created in the ng-repeat. The requirement is that as many Contacts as the User needs can be created, and are stored inside an array called c.data.alternate_contacts. The <sn-record-picker> simply references the 'sys_user' table and I would like to return the sys_id in the 'value' attribute of the record picker object.

find_real_file.png

HTML Template:

<div class="row" ng-repeat="item in data.alternate_contacts track by $index">
    <div class="col-md-4 p4p-form--block">
    <label>Contact Name<font color="#ff0000">*</font></label>
    <sn-record-picker multiple="false" class="treas-record-picker" field="c.contact_name" table="'sys_user'" display-        field="'name'" value-field="'sys_id'" search-fields="'name'" page-size="150" ></sn-record-picker>
    </div>
</div>

The issue with using the controller Client Script provided in the link above:

$scope.location = {

      displayValue: c.data.loc.name,

      value: c.data.loc.sys_id,

      name: 'location'

};

 

$scope.$on("field.change", function(evt, parms) {

      if (parms.field.name == 'location')

              c.data.setLocation = parms.newValue;

     

      c.server.update().then(function(response) {        

              spUtil.update($scope);

      })

});

Is that this only tracks one specific <sn-record-picker> object where the field is known (field='location'), however I need to come up with a way to be able to reference multiple unique <sn-record-picker> objects with unique field values and track the value stored in each one within the array of objects being stored in the ng-repeat data array.

So my question is, can this be done using the field attribute of <sn-record-picker>? Or if there is a more feasible option, I am open to any and all suggestions. 

1 ACCEPTED SOLUTION

Jon Barnes
Kilo Sage

you will need to provide some separation in your data model, so I would do something like this where each field is stored directly in that array. let me know if this gets you the idea you need to make this work. If not, please post your entire widget and I should be able to help you sort it out.

c.data.alternate_contacts = [];

c.addContact = function() {
  c.data.alernate_contacts.push({
    value: '',
    displayValue: '',
    name: 'contact'
  });
};

Your html would change slightly to look like this:

<div class="row" ng-repeat="item in data.alternate_contacts track by $index">
  <div class="col-md-4 p4p-form--block">
    <label>Contact Name<font color="#ff0000">*</font></label>
    <sn-record-picker multiple="false" class="treas-record-picker" field="item" table="'sys_user'" display-field="'name'" value-field="'sys_id'" search-fields="'name'" page-size="150" ></sn-record-picker>
  </div>
</div>

 

View solution in original post

9 REPLIES 9

Thank you so much Jon, this method worked very well and I was able to complete the widget.

Hi Jeremy,

I hope you're well in these unusual times,

I am working on something similar where I have 2 record pickers pointing to the sys_user table, I have been able to get them to populate onLoad with the logged in user but if I change one of them it also affects the other record picker as well, need them to be unique, not sure if you are able to post your solution on here? I think my problem is my scope in the client script.

My community question if would like to see the code:

https://community.servicenow.com/community?id=community_question&sys_id=0dae86e81b7cd010a59033f2cd4b...

Kind Regards

Ashley

 

Hi Ashley,

I believe what you need to do is create multiple instances of a Controller in the client. The reason that one update changes all of your sn-record-pickers is that I'm assuming you only have one instance of $scope.name in your client script instead of creating multiple controllers for multiple sn-record-pickers (looking at your post). Essentially you are just replicating a single controller multiple times, instead of creating multiple unique controllers.

I suggest initializing multiple controllers in the client using a for loop for however many items are in the User's cart onLoad. 

This is how I accomplished this.

Client:

/* Store length to loop through (This should be the number of items in your User's cart) */
var itemLength = c.data.items.length;
for (var y = 0; y < itemLength ; y++) {
    c.addContact(c.data.items[y].value, c.data.items[y].displayValue);
}

/* Function to create unique controller using loop */
c.addContact = function(val, dVal) {
		/* Multiple Unique controllers */
		c.data.alternate_contacts.push({
			value: val,
			displayValue: dVal,
			name: 'contact'
		});
	}

Server:

/* Server Code to Populate array to loop through (you should find all Cart Items here) */
		var gr= new GlideRecord('table_name');
		
		gr.query();
		data.items= [];
		while(gr.next()) {
			var item = {};
			item.value = gr.sys_id.toString();
			item.displayValue = gr.name.toString();
			data.items.push(item);
		}
		/* Alternate Contacts */

 

Then in your HTML use ng-repeat as you have been before. Feel free to reach out if you need further clarification.

Hi Jeremy,

I hope you're doing well, took a bit of a break from this piece of work.

So things have moved on and I am now using a select, its almost done but I have one last piece to resolve which is probably the hardest bug so reaching out for help.

So I managed to get further with the cart and had the requested for field set to the logged in user which was fine, they could change the name the hardware was for which then updated the cart database.

The problem arises if the user disappears or refreshes the browser, the fields would show the logged in user but behind the scenes it would show the correct person in the database so this could be confusing for the user... always like to find a way to break our systems.

I have managed to get the sysid and display name of the requested for fields using your code above from the "sc_item_option" table.

 

Server:

	data.items = [];
	var grReqFor = new GlideRecord('sc_item_option');
	grReqFor.addEncodedQuery('cart_item!=NULL^sys_updated_by='+data.email+'^item_option_new=7fb90fd513b467003f035dcf3244b0e4');
	grReqFor.query();
	while(grReqFor.next()) {
		var item = {};
		var grDisplayName = new GlideRecord('sys_user');
		grDisplayName.get('sys_id', grReqFor.value);
		item.value = grDisplayName.sys_id.toString();
		item.displayValue = grDisplayName.name.toString();
		data.items.push(item);
	}

Result:

find_real_file.png

My problem is in the client script, I keep getting the error:

TypeError: c.addContact is not a function
    at api.controller.sc-shopping-cart-newv3 (sc-shopping-cart-newv3.js:10:7)

Client Side Code:

	var itemLength = c.data.items.length;
for (var y = 0; y < itemLength ; y++) {
    c.addContact(c.data.items[y].value, c.data.items[y].displayValue);
}
/* Function to create unique controller using loop */
c.addContact = function(val, dVal) {
		/* Multiple Unique controllers */
		c.data.alternate_contacts.push({
			value: val,
			displayValue: dVal,
			name: 'contact'
		});
	}

$scope.userModel = $scope.data.alternate_contacts;

Here is the HTML code if you need it, the select are inside an ng-repeat for the number of cart items, so in this case their would be 2 Requested For fields and 2 Addresses.

What I want to try and achieve is the requested for fields to show the correct user from the database for each piece of hardware:

<select ng-model="userModel" name="userModel_desktop{{$index}}" ng-options="userModel.name for userModel in data.userList track by userModel.sys_id"            
  ng-change="c.updateRequestedFor(item.sys_id, userModel.sys_id)" id="userModel_desktop{{$index}}" class="dropdown pull-right hidden-xs" style="height: 30px; width: 235px" required>
</select>

<select ng-model="address" name="address_desktop{{$index}}" ng-options="address.name for address in data.userAddress | filter:{u_active_address:'true'} track by address.name"
  ng-change="c.updateAddress(item.sys_id, address.value)" id="address_desktop{{$index}}" class="dropdown pull-right hidden-xs" ng-class="{address_invalid_desktop: myForm['address_desktop' + $index].$invalid, address_valid_desktop: myForm['address_desktop' + $index].$valid}" required>
<option style="display:none" value="">Select a Address</option>
</select>

Hopeful outcome:

find_real_file.png

Any suggestions and I would be grateful

Kind Regards

Ashley

@Jon Barnes Can you please check the link

 

https://community.servicenow.com/community?id=community_question&sys_id=9383043e1bcc8110c552c8031d4bcb8d

 

I need some suggestions on how to accomplish this