샘플 프로젝트 및 사용 사례 > 사용 사례: 3D-Guided Service Instructions > 3D-Guided Service Instructions 302: Vuforia Studio에 간단한 ThingWorx 서비스 추가 > GetPriceAvailability 호출 및 serviceInvokeComplete 이벤트 수신기 사용
  
GetPriceAvailability 호출 및 serviceInvokeComplete 이벤트 수신기 사용
getPriceAvailability 서비스가 Vuforia Studio에 추가되면 조작을 위해 데이터에 액세스할 수 있도록 이를 경험에서 호출해야 합니다. 이렇게 하려면 3D-Guided Service Instructions 301에서 코드를 업데이트해야 합니다. 또한 경험에서 이 서비스의 데이터에 액세스할 수 있도록 하려면 새 이벤트 수신기를 코드에 추가해야 합니다.
* 
이 섹션의 전체 코드는 GitHub의 Appendix 1에서 제공됩니다.
1. Home.js를 엽니다.
2. userpick 이벤트 수신기에서 다음과 같이 변경합니다.
a. priceString 변수를 삭제합니다. 이제 모델의 속성이 아닌 Vuforia StudioshoppingThing에서 데이터에 액세스할 수 있습니다.
b. meta라는 이름의 객체를 만들고 스크립트의 다른 함수에서 액세스할 수 있도록 응용 프로그램의 범위로 설정합니다. partName, instructionNamepartNumber 변수를 meta 객체의 속성이 되도록 변경합니다.
//
// create an object with the properties below that are based on attribute names from Creo Illustrate for this model. Use metadata.get to obtain the data from the JSON properties for this occurrence.
$scope.meta = {
partName : metadata.get(pathId, 'Display Name'),
instructionName : metadata.get(pathId, 'illustration'),
partNumber : metadata.get(pathId, 'partNumber'),
} // $scope.meta end
c. price 변수 및 priceInfo 응용 프로그램 매개 변수는 priceString 변수에 직접 종속되어 있으므로 삭제합니다.
d. meta 객체에서 partNamepartNumber를 호출하기 위한 새 구문과 일치하도록 itemNameitemNumber 응용 프로그램 매개 변수의 정의를 편집합니다. 그런 다음 target(응용 프로그램 범위)라는 새 변수를 만들고 이를 userpick 이벤트의 targetName 변수로 설정합니다.
//
// set itemName app parameter to be equal to the partName variable, same relationship with itemNumber and partNumber and priceInfo and price.
// Set the itemCount to 1 for the purpose of this section, since it is not hooked up to an actual inventory.
$scope.app.params.itemName = $scope.meta.partName;
$scope.app.params.itemNumber = $scope.meta.partNumber;
$scope.app.params.itemCount = 1;

$scope.target = targetName;
e. 다음 코드를 추가하여 shoppingThing 내에서 getPriceAvailability 서비스를 트리거합니다. 이 코드는 twx.app.fn.triggerDataService(‘<TWX Entity>’, ‘<TWX Service>’, ‘<매개 변수>’) 형식을 따릅니다. triggerDataService 함수는 ThingWorxVuforia Studio에서 제공하는 서비스를 호출합니다. 여기서 서비스가 사용하는 입력 매개 변수와 함께 ThingWorx 엔티티 및 서비스가 호출됩니다. 이 경우 호출되는 사물은 모든 부품 정보를 보유하는 shoppingThing, 호출되는 서비스(getPriceAvailability) 및 선택한 부품의 부품 번호에 대한 매개 변수인 매개 변수(pid)입니다. 이 서비스의 출력은 serviceInvokeComplete 이벤트 수신기를 호출하는 비동기 콜백을 사용하여 호출됩니다. serviceInvokeComplete 이벤트 수신기는 이 섹션의 뒷부분에서 생성됩니다.
//
// call the getPriceAvailability ThingWorx service based on partNumber
twx.app.fn.triggerDataService('shoppingThing', 'getPriceAvailability', {pid: $scope.meta.partNumber})
f. 아래 팝업에 대한 if 문은 userpick 이벤트 수신기에서 serviceInvokeComplete 이벤트 수신기로 이동됩니다. PTC Metadata API 닫는 대괄호, catch 문 및 userpick 닫는 대괄호를 복사하여 서비스 트리거 아래에 붙여넣습니다. 팝업의 Disassemble 함수 아래에서 방금 복사한 코드를 삭제해야 합니다. 그렇지 않으면 추가 괄호가 생깁니다.
}) //end brackets for PTC API and .then
//
//catch statement if the promise of having a part with metadata is not met
.catch( (err) => { console.log('metadata extraction failed with reason : ' +err) })
}) //end brackets for userpick function. Will continue to move throughout code
3. userpick 이벤트 수신기가 업데이트되었으므로 이제 serviceInvokeComplete 이벤트 수신기를 만들겠습니다. 이 이벤트 수신기는 getPriceAvailability 서비스가 완료된 후에만 활성화됩니다.
a. 팝업의 if 문 위에 이벤트 수신기에 대한 다음 코드를 추가합니다. if 문의 시작부터 disassemble 함수 끝까지 코드를 선택하고 내어쓰기하여 올바르게 정렬합니다.
* 
끝 괄호가 아직 생성되지 않았기 때문에 이 줄 옆의 빨간색 원 안에 X가 표시됩니다. 이는 정상입니다.
$scope.$on('getPriceAvailability.serviceInvokeComplete', function(evt) {
b. ThingWorx에서 동적으로 데이터가 추가되므로 이제 팝업을 호출하는 프로세스가 변경됩니다. serviceInvokeComplete 이벤트 수신기 아래에 rowData라는 변수를 생성합니다. 이 변수는 getPriceAvailability 서비스로 생성된 인포테이블의 현재 행을 호출합니다.
//
// variable holding all data for the current row in the infotable
var rowData = twx.app.mdl['shoppingThing'].svc['getPriceAvailability'].data.current
c. 이제 스크립트 작성을 추가하여 선택한 객체의 가격을 결정합니다. price 변수는 ThingWorx의 정보를 사용하며 선택한 행의 부품 가격이 표시된 문자열(앞에 $ 표시) 또는 부품을 사용할 수 없는 경우 ‘UNAVAILABLE’로 설정됩니다. price 변수가 사용하는 정보는 부품 가격을 확인하기 전에 부품을 사용할 수 있는지 확인하는 조건부 연산자를 사용하여 생성됩니다. 동일한 조건의 if 문을 사용하여 수행할 수도 있습니다. 유사한 로직을 통해 카트의 가격을 합산하는 데 사용되는 priceInfo 응용 프로그램 매개 변수를 작성합니다.
//
// price is going to be the variable that is referenced in the popup, while the app parameter priceInfo will be used for adding the total in the cart
var price = rowData.avail === true ? '$' + rowData.price
: 'UNAVAILABLE';
$scope.app.params.priceInfo = rowData.avail === true ? parseFloat(rowData.price)
: undefined ;
d. 값에 쉽게 액세스할 수 있도록 $scope.meta 객체를 로컬 객체로 이벤트 수신기로 가져오는 데 사용할 meta라는 변수를 만듭니다.
//
// create a variable to bring the $scope.meta object into this event listener as a local object
let meta = $scope.meta
e. shoppingThing의 정보를 기반으로 부품의 사용 가능 여부를 결정하기 위해 팝업에 추가 정보가 더해지므로 이제 이후 작업에서 생성할 함수를 사용하여 팝업 템플릿을 설정합니다. 따라서 지금은 앞서 만든 if 문에 대한 조건과 닫는 대괄호를 삭제하고 팝업을 호출하기 위한 코드를 내어씁니다.
f. 이전에 if 문의 else 사례였을 팝업 코드를 삭제합니다. 또한 template 속성 값을 삭제합니다. 이는 다음 단계에서 대체됩니다. 이러한 코드 줄 옆에 오류 표시기가 있지만 이는 템플릿 값이 채워지면 해결됩니다.
g. template 속성에는 meta 객체의 입력과 선택한 부품에 대한 price 변수를 사용하여 setTemplate 함수(나중에 생성됨)를 호출하는 값이 있어야 합니다. 이제 팝업을 호출하는 코드가 아래 표시된 코드와 일치해야 합니다.
//
// adds an ionic popup when a part is clicked
$scope.popup = $ionicPopup.show({
//
//call the function for setting the template
template: $scope.setTemplate(meta, price),
//
// set the scope for the popup
scope: $scope
}); //end of ionic popup
h. disassemble 함수의 경우 모델의 애니메이션 시퀀스를 설정하기 위한 modelObjectmodelinstruction 속성을 업데이트해야 합니다. 여기에서 이전에 생성된 $scope.target 변수에 액세스하고 instructionNamemeta 객체 내부의 새 위치로 업데이트됩니다.
//
// function to be bound to the Disassemble button in the popup
$scope.disassemble = function () {
//
// set an object that targets the model and its instruction property
var modelObject = { model: $scope.targetName,
instruction: 'l-Creo 3D - ' + meta.instructionName + '.pvi' };
//
// set the sequence for the quadcopter to be the name of the associated instruction
$scope.view.wdg.quadcopter.sequence = modelObject.instruction
} //disassemble function end
i. 끝 괄호와 괄호를 추가하여 getPriceAvailability 서비스 호출을 완료합니다.
}) // getPriceAvailability end
4. 경험에서 Ionic 팝업을 사용하는 프로세스를 간소화하려면 다른 userpick 이벤트 수신기를 추가해야 합니다. 이 이벤트 수신기는 다른 부품을 클릭하기 전에 닫히지 않은 팝업을 닫는 데 사용됩니다. 이 이벤트 수신기가 트리거되면 클릭한 첫 번째 부품에 대한 팝업을 닫고 선택된 다음 부품에서 새 팝업 및 음영을 열기 전에 해당 셰이더를 제거합니다.
//
// userpick event listener that will take place when you click on another part without choosing an option in the popup
$scope.$on('userpick', function (event, targetName, targetType, eventData) {

//
// close the popup and remove the shader from the part even when you didn't chose an option from the popup
$scope.popup.close()

$scope.hiliteOff()

}) // closing userpick end
5. 경험을 업데이트하는 마지막 단계로 앞서 언급한 setTemplate 함수를 만듭니다. 이 함수를 통해 팝업에서 부품에 대해 사용 가능한 정보를 결정하는 데 사용할 template 변수를 설정합니다. 여기에는 부품과 연관된 어셈블리 분해 시퀀스가 있는지, shoppingThing에서 사용 가능한지 결정하는 로직이 포함됩니다.
a. clearCart 함수 아래에 metaprice 입력을 사용하여 setTemplate라는 함수를 만듭니다.
//
// function for setting the template for the Ionic popup
$scope.setTemplate = function (meta, price) {

} // setTemplate end
b. 먼저 함수에 로직을 추가하여 선택한 부품과 연관된 어셈블리 분해 시퀀스가 있는지 결정합니다. instr 변수는 조건부 연산자를 사용하여 metainstructionName 속성이 채워졌는지 여부를 확인하고 이전의 if 문을 대체합니다. 연관된 시퀀스가 있는 경우 클릭하여 disassemble 함수를 트리거하고 셰이더를 끄고 팝업을 닫을 수 있는 버튼이 생성됩니다. 연관된 시퀀스가 없는 경우 instr은 빈 문자열이 됩니다.
//
// if there is a disassembly sequence associated with the part, create a Disassemble button in the popup, if not, no button will appear
var instr = meta.instructionName.length > 0 ? '<div class="btndisassemble" ng-click="hiliteOff();popup.close();disassemble();">Disassemble</div>'
: '';
c. 또한 부품을 사용할 수 있는지 여부를 결정하는 로직을 추가해야 합니다. shoppingThing의 정보를 기반으로 부품에 연관된 가격이 있는지 여부를 판별하는 조건부 연산자의 결과로 addTo 변수를 생성합니다. 있는 경우 addToCart 함수를 트리거하는 클릭 가능한 Add to Cart 버튼과 함께 가격이 팝업에 표시됩니다. 없는 경우에는 가격만 팝업에 추가됩니다.
//
// if price != unavailable, define an add to cart button and have the price displayed in the popup, if it is unavailable, just display price
var addTo = price != 'UNAVAILABLE' ? price + '&nbsp; </div><div class="btnadd" ng-click="hiliteOff();popup.close();addToCart();">Add to Cart</div>'
: price ;
d. 팝업에 사용 가능한 버튼을 결정하면 이전 팝업에 대해 생성한 것과 동일한 template 변수를 생성합니다. 팝업에는 부품에 적용되는 버튼과 함께 선택한 부품의 수량, 부품 번호, 부품 이름 및 가격이 표시됩니다. Continue 버튼을 다시 한번 추가하여 팝업을 닫습니다. template 변수에 대한 return 문을 추가하여 실행 시 함수에서 출력합니다. 이로써 setTemplate 함수를 완료합니다.
//
// build the template for the popup
var template = '<div>' + $scope.app.params.itemCount + 'x  ' + meta.partNumber +
'&nbsp;</br>' + meta.partName +
'&nbsp;</br>' + addTo + instr +
'<div class="btncontinue" ng-click="hiliteOff();popup.close();">Continue</div>' ;

//
// return the template variable when this function is called
return template
6. 미리 보기를 클릭합니다.
a. 쿼드콥터의 베이스를 클릭하면 사용 가능한 부품이 포함되어 있지만 연관된 어셈블리 분해 시퀀스가 없는 팝업 옵션을 볼 수 있습니다. Continue를 클릭하여 팝업을 닫습니다.
* 
다른 부품을 클릭하기 전에 Continue 버튼을 클릭하지 않으면 첫 번째 팝업이 닫히지 않고 서로 겹쳐집니다.
b. 로터 중 하나를 선택하여 연관된 어셈블리 분해 시퀀스가 있고 사용 가능한 부품을 표시하는 팝업을 확인합니다.
c. 쿼드콥터의 상단을 선택하여 연관된 어셈블리 분해 시퀀스가 없고 사용할 수 없는 부품을 표시하는 팝업을 확인합니다.
부품에 연관된 어셈블리 분해 시퀀스가 있지만 사용할 수 없을 때 표시되는 팝업을 보려면 모터를 선택합니다.
7. 이제 이 섹션이 완료되었습니다. 이 섹션의 기타 모든 코드는 3D-Guided Service Instructions 301에 있는 것과 동일합니다. 부품을 클릭할 수 있고 팝업이 표시되어야 하며 팝업에 위의 네 가지 옵션 중 하나가 제공되어야 합니다. 해당되는 경우 카트에 부품을 추가할 수 있어야 합니다. userInput 위젯을 사용하여 부품을 검색할 수도 있어야 합니다.
이 사용 사례의 마지막 섹션에서는 ThingWorx를 사용하여 지속 쇼핑 카트 생성 방법을 살펴보겠습니다.