angular-ui-grid load state on controller creation - angular-ui-grid

I want to load the last state of a ui-grid when the controller is created.
I've set up a plunkr. http://plnkr.co/edit/cOhLUUABVStfoTH6QVZd?p=preview
In my controller I use a restoreOnLoad function which is called immediately at the end of the controller. I get a TypeError: Cannot read property 'saveState' of undefined because the gridApi is undefined at the time of controller creation.
restoreOnLoad = function(){
//retrieve stateinfo. For simplicity: inline object...
var stateInfo = {
"columns": [
{
"name": "",
"visible": true,
"width": 50,
"sort": {
"direction": "asc",
"priority": 0
},
"filters": [
]
},
{
"name": "M",
"visible": true,
"width": 50,
"sort": {
},
"filters": [
]
},
{
"name": "Company",
"visible": true,
"width": 200,
"sort": {
},
"filters": [
]
}
],
"scrollFocus": {
},
"selection": {
}
}
$scope.gridApi.saveState.restore($scope, stateInfo);
}
restoreOnLoad();
Any ideas how to avoid this error?

Your restore call is running before the grid's API is registered, so you just need to wait for that to happen.
You can move your restoreOnLoad() call to your onRegisterApi handler:
vm.gridOptions = {
onRegisterApi: function (gridApi) {
$scope.gridApi = gridApi;
restoreOnLoad();
}
}

Related

Twilio Voice - allow current caller to listen to previous caller's recorded voicemail

Hoping someone can point me in the right direction. I'd like to be able to do this via Twilio Studio. If not, I can learn TwiML. That's about as far as my brain will stretch.
I've made a simple flow in Twilio Studio that enables the caller to record a voicemail. I would like to add an option for the current caller to be able to play the previous caller's recorded voicemail. I think I need to use a Say/Play widget for this. What do I need to use for the "URL of audio file" so that the previous recorded voicemail is played? I assume this URL will change every time that a caller leaves a voicemail, so it'll need to auto-update. Can I use "RecordingURL" somehow? Is there a solution using TwiML? Any help appreciated
Thanks!
Without a way to keep state between the call, this is not possible. The best way would be to have some type of DB you can store the recording SID and reference it in a future flow. You can use a tool like Twilio Sync to do this or Airtable, but it does require code.
I can't think of a way to do this without involving some coding.
Alternate ways are to list the recording records and pull the most recent one off the list but not ideal since you don't know when the last recording occurred.
Another approach is to modify the webhook associated with your Twilio phone number for that Studio Flow to pass in the last Recording SID into your flow and you use that SID to dynamically construct the recording URL to playback, as shown in the JSON flow below (which you can import when creating a new Studio Flow).
Don't forget to set the recordingStatuscallback to the above Twilio Function which updates the phone number webhook to pass in the recording SID. You set your Recording Status Callback of your Record Voicemail Widget to point to your unique Function domain and function path (currently set to: https://magnolia-kitty-3234.twil.io/phUpdate).
Feel free to improve on the below and share.
Twilio Function Code
exports.handler = function(context, event, callback) {
let client = context.getTwilioClient();
const telephoneNumAccountSid = "PN..."; \\ set this to your Phone Number SID
const accountSid = event.AccountSid;
const studioFlowSid = "FW..."; \\ set this to your Studio Flow SID
const webhookUrl = `https://webhooks.twilio.com/v1/Accounts/${accountSid}/Flows/${studioFlowSid}`;
const recordingSid = event.RecordingSid;
client
.incomingPhoneNumbers(telephoneNumAccountSid)
.update({ voiceUrl: `${webhookUrl}?recording=${recordingSid}`, voiceFallbackUrl: `${webhookUrl}?recording=${recordingSid}` })
.then (result => {
console.log(result.voiceUrl);
callback(null, "success");
})
.catch(err => {
console.log(err);
callback("error");
});
};
Studio Flow JSON
{
"description": "A New Flow",
"states": [
{
"name": "Trigger",
"type": "trigger",
"transitions": [
{
"event": "incomingMessage"
},
{
"next": "set_variables_1",
"event": "incomingCall"
},
{
"event": "incomingRequest"
}
],
"properties": {
"offset": {
"x": 0,
"y": 0
}
}
},
{
"name": "split_1",
"type": "split-based-on",
"transitions": [
{
"next": "say_play_2",
"event": "noMatch"
},
{
"next": "gather_1",
"event": "match",
"conditions": [
{
"friendly_name": "{{trigger.call.recording}}",
"arguments": [
"{{trigger.call.recording}}"
],
"type": "is_not_blank",
"value": "Is Not Blank"
}
]
}
],
"properties": {
"input": "{{trigger.call.recording}}",
"offset": {
"x": 110,
"y": 350
}
}
},
{
"name": "say_play_1",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"play": "https://api.twilio.com/2010-04-01/Accounts/{{flow.variables.accountSid}}/Recordings/{{flow.variables.recording}}.mp3",
"offset": {
"x": 450,
"y": 1110
},
"loop": 1
}
},
{
"name": "set_variables_1",
"type": "set-variables",
"transitions": [
{
"next": "split_1",
"event": "next"
}
],
"properties": {
"variables": [
{
"value": "{{trigger.call.recording}}",
"key": "recording"
},
{
"value": "{{trigger.call.AccountSid}}",
"key": "accountSid"
}
],
"offset": {
"x": 60,
"y": 170
}
}
},
{
"name": "say_play_2",
"type": "say-play",
"transitions": [
{
"next": "record_voicemail_1",
"event": "audioComplete"
}
],
"properties": {
"voice": "Polly.Joanna-Neural",
"offset": {
"x": -120,
"y": 650
},
"loop": 1,
"say": "Please leave a message at the beep!",
"language": "en-US"
}
},
{
"name": "record_voicemail_1",
"type": "record-voicemail",
"transitions": [
{
"event": "recordingComplete"
},
{
"event": "noAudio"
},
{
"event": "hangup"
}
],
"properties": {
"transcribe": false,
"offset": {
"x": -120,
"y": 860
},
"trim": "trim-silence",
"play_beep": "true",
"recording_status_callback_url": "https://magnolia-kitty-3234.twil.io/phUpdate",
"timeout": 5,
"max_length": 3600
}
},
{
"name": "gather_1",
"type": "gather-input-on-call",
"transitions": [
{
"next": "split_2",
"event": "keypress"
},
{
"event": "speech"
},
{
"event": "timeout"
}
],
"properties": {
"number_of_digits": 1,
"speech_timeout": "auto",
"offset": {
"x": 300,
"y": 650
},
"loop": 1,
"finish_on_key": "#",
"say": "There is a previous recording, press 1 if you want to listen to it or 2 if you want to leave a new voicemail.",
"stop_gather": true,
"gather_language": "en",
"profanity_filter": "true",
"timeout": 5
}
},
{
"name": "split_2",
"type": "split-based-on",
"transitions": [
{
"event": "noMatch"
},
{
"next": "say_play_1",
"event": "match",
"conditions": [
{
"friendly_name": "If value equal_to 1",
"arguments": [
"{{widgets.gather_1.Digits}}"
],
"type": "equal_to",
"value": "1"
}
]
},
{
"next": "say_play_2",
"event": "match",
"conditions": [
{
"friendly_name": "If value equal_to 2",
"arguments": [
"{{widgets.gather_1.Digits}}"
],
"type": "equal_to",
"value": "2"
}
]
}
],
"properties": {
"input": "{{widgets.gather_1.Digits}}",
"offset": {
"x": 280,
"y": 870
}
}
}
],
"initial_state": "Trigger",
"flags": {
"allow_concurrent_calls": true
}
}
Alan

Is there a way to enter dollars/cents via DTMF?

I'm currently working on a pay-by-phone system with this flow:
<Gather> customer's account number with us
<Gather> amount to pay
<Pay> - with the previous 2 <Gather>s passed as variables to Stripe
The issue I am having is collecting the amount. Is there a way to convert the amount from a single string to a decimal string?
eg. 12300 becomes 123.00
All suggestions welcome as the current working theory is to add a third <Gather> just for the cents but this feels cumbersome from a UX/UI perspective.
I edited this answer, since I was successful in accomplishing this.
I did it with STUDIO and FUNCTIONS.
Prompt the caller to enter the amount using star key () as a decimal. For example to enter $5.43 they should enter 543 and then press #.
then I took the gathered digits and sent them out as a parameter in the FUNCTION widget by inserting "amount" as the KEY and {{widgets.gather_1.digits}} as the VALUE of the parameter.
I wrote the following function:
exports.handler = function(context, event, callback) {
const rawamount = event.amount;
const decimalamount = rawamount.replace("*",".");
const response = decimalamount;
callback(null, response);
};
then in the <pay> widget I inserted {{widget.function_1.body}} in the AMOUNT field.
Your payment should process with dollars and cents.
Let me know if this worked. If you need screenshots of my studio flow let me know.
ADDED:
Here is a sample flow if someone wants to use as a template (someone requested it)
Open a blank Studio flow.
In the trigger widget under "config" tab click on "SHOW FLOW JSON" delete all previous code and paste code below inside and save.
Your flow should generate.
Then create function. Code and instructions below after JSON.
PLEASE NOTE: though a function may pop up in the flow, you won't be able to access it, and will have to create it under your account.
{
"description": "A New Flow",
"states": [
{
"name": "Trigger",
"type": "trigger",
"transitions": [
{
"event": "incomingMessage"
},
{
"next": "gather_1",
"event": "incomingCall"
},
{
"event": "incomingRequest"
}
],
"properties": {
"offset": {
"x": 0,
"y": 0
}
}
},
{
"name": "gather_1",
"type": "gather-input-on-call",
"transitions": [
{
"next": "function_1",
"event": "keypress"
},
{
"event": "speech"
},
{
"event": "timeout"
}
],
"properties": {
"voice": "man",
"speech_timeout": "auto",
"offset": {
"x": 30,
"y": 270
},
"loop": 1,
"finish_on_key": "#",
"say": "Please enter an amount, then press pound. To enter with cents use the star key. for example to enter $6.25 press six star two five.",
"language": "en-US",
"stop_gather": true,
"gather_language": "en",
"profanity_filter": "true",
"timeout": 5
}
},
{
"name": "function_1",
"type": "run-function",
"transitions": [
{
"next": "say_play_2",
"event": "success"
},
{
"event": "fail"
}
],
"properties": {
"offset": {
"x": -60,
"y": 530
},
"parameters": [
{
"value": "{{widgets.gather_1.Digits}}",
"key": "amount"
}
],
"url": "https://charcoal-sloth-2579.twil.io/insert-decimal"
}
},
{
"name": "say_play_2",
"type": "say-play",
"transitions": [
{
"next": "pay_1",
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": -59,
"y": 839
},
"loop": 1,
"say": "You entered ${{widgets.function_1.body}}",
"language": "en-US"
}
},
{
"name": "pay_1",
"type": "capture-payments",
"transitions": [
{
"next": "say_play_3",
"event": "success"
},
{
"next": "say_play_4",
"event": "maxFailedAttempts"
},
{
"next": "say_play_4",
"event": "providerError"
},
{
"next": "say_play_4",
"event": "payInterrupted"
},
{
"next": "say_play_4",
"event": "hangup"
},
{
"next": "say_play_4",
"event": "validationError"
}
],
"properties": {
"security_code": true,
"offset": {
"x": -70,
"y": 1140
},
"max_attempts": 2,
"payment_connector": "Stripe_Connector",
"payment_amount": "{{widgets.function_1.body}}",
"currency": "usd",
"language": "en-US",
"postal_code": "false",
"payment_token_type": "one-time",
"timeout": 5,
"valid_card_types": [
"visa",
"master-card",
"amex",
"discover"
]
}
},
{
"name": "say_play_3",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": -185,
"y": 1433
},
"loop": 1,
"say": "Your payment ${{widgets.function_1.body}}was successful, Thank you.",
"language": "en-US"
}
},
{
"name": "say_play_4",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": 190,
"y": 1456
},
"loop": 1,
"say": "There was an error with your payment. Goodbye!",
"language": "en-US"
}
}
],
"initial_state": "Trigger",
"flags": {
"allow_concurrent_calls": true
}
}
Create a function with this code:
exports.handler = function(context, event, callback) {
const amount = event.amount;
const convert = amount.replace('*', '.');
callback(null, convert);
};
Make sure in the flow you check that the function widget has the right function selected and that you insert the following parameter KEY:amount VALUE: {{widgets.YOUR_GATHER_WIDGET NAME.body}}

Can't create ad group

Campaign:
{
"id": "1709932214",
"name": "Noah Baumbach",
"status": "ENABLED",
"servingStatus": "PENDING",
"startDate": "20190219",
"endDate": "20190220",
"budget": {
"budgetId": "1730787529",
"name": "Dominic Schumm",
"amount": {
"ComparableValue.Type": "Money",
"microAmount": "10000"
},
"deliveryMethod": "STANDARD",
"referenceCount": 5,
"isExplicitlyShared": true,
"status": "ENABLED"
},
"conversionOptimizerEligibility": {
"eligible": false,
"rejectionReasons": [
"CONVERSION_TRACKING_NOT_ENABLED"
]
},
"adServingOptimizationStatus": "OPTIMIZE",
"frequencyCap": {
"impressions": "100",
"timeUnit": "DAY",
"level": "CAMPAIGN"
},
"settings": [
{
"attributes": {
"xsi:type": "GeoTargetTypeSetting"
},
"Setting.Type": "GeoTargetTypeSetting",
"positiveGeoTargetType": "DONT_CARE",
"negativeGeoTargetType": "DONT_CARE"
}
],
"advertisingChannelType": "DISPLAY",
"networkSetting": {
"targetGoogleSearch": false,
"targetSearchNetwork": false,
"targetContentNetwork": true,
"targetPartnerSearchNetwork": false
},
"biddingStrategyConfiguration": {
"biddingStrategyType": "MANUAL_CPC"
},
"campaignTrialType": "BASE",
"baseCampaignId": "1709932214"
}
When I try to create ad group with below parameters:
{
campaignId: 1709932214,
name: 'Jess Hegmann',
status: 'ENABLED',
criterionTypeGroup: 'VERTICAL',
targetAll: false,
bidAmount: 10000,
biddingStrategyType: 'MANUAL_CPC',
biddingStrategyName: 'biddingName 123',
adGroupType: 'DISPLAY_STANDARD',
adGroupAdRotationMode: 'OPTIMIZE',
};
Google adwords give me an error:
Failed: "[OperationAccessDenied.OPERATION_NOT_PERMITTED_FOR_CAMPAIGN_TYPE # operations[0].operand.biddingStrategyConfiguration.biddingStrategyType; trigger:'DISPLAY']"
How can I solve this?
I think this might be the reason:
Starting with v201705, this field cannot be set at the ad group or ad group criterion level to any value other than BiddingStrategyType.NONE.
Bid Strategy AdGroup Google Ads

"Invalid JSON payload received." when creating a new sheet

I'm just starting out with the Sheets API, and I'm following this setup to create a new sheet: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/create
In this code, the spreadsheetBody object is empty. For this I've used the example on this page: https://developers.google.com/sheets/api/samples/writing and added this code to the spreadsheetBody variable:
{
"range": "Sheet1!A1:D5",
"majorDimension": "ROWS",
"values": [
["Item", "Cost", "Stocked", "Ship Date"],
["Wheel", "$20.50", "4", "3/1/2016"],
["Door", "$15", "2", "3/15/2016"],
["Engine", "$100", "1", "30/20/2016"],
["Totals", "=SUM(B2:B4)", "=SUM(C2:C4)", "=MAX(D2:D4)"]
],
}
However, when I post this I get the error "Invalid JSON payload received. Unknown name "range" at 'spreadsheet': Cannot find field.". What might be wrong here?
The request body you are using is for spreadsheets.values.update.
As a sample, the request body for creating Spreadsheet is as follows. In this sample request body, ["Item", "Cost", "Stocked", "Ship Date"], ["Wheel", "$20.50", "4", "3/1/2016"] was used from the document you use.
Sample request body:
{
"properties":
{
"title": "sampleSpreadsheet"
},
"sheets":
[
{
"data":
[
{
"startRow": 0,
"startColumn": 0,
"rowData":
[
{
"values":
[
{
"userEnteredValue":
{
"stringValue": "Item"
}
},
{
"userEnteredValue":
{
"stringValue": "Cost"
}
},
{
"userEnteredValue":
{
"stringValue": "Stocked"
}
},
{
"userEnteredValue":
{
"stringValue": "Ship Date"
}
}
]
},
{
"values":
[
{
"userEnteredValue":
{
"stringValue": "Wheel"
}
},
{
"userEnteredValue":
{
"numberValue": 20.5
},
"userEnteredFormat":
{
"numberFormat":
{
"type": "NUMBER",
"pattern": "$##.00"
}
}
},
{
"userEnteredValue":
{
"numberValue": 4
}
},
{
"userEnteredValue":
{
"numberValue": 42372
},
"userEnteredFormat":
{
"numberFormat":
{
"type": "DATE",
"pattern": "d/m/yyyy"
}
}
}
]
}
]
}
]
}
]
}
Note:
When this request body is used for spreadsheets.create, a Spreadsheet with the filename of sampleSpreadsheet is created. The sheet has the values of ["Item", "Cost", "Stocked", "Ship Date"], ["Wheel", "$20.50", "4", "3/1/2016"] at "A1:D2".
References:
spreadsheets.create- spreadsheets.batchUpdate

Send ajax request in PeriodSelector AMChart

I have this javascript code to draw an amchart stock, just the PeriodSelector part:
var periodSelectorAjax = new AmCharts.PeriodSelector();
periodSelectorAjax.periods = [
{period: "F1", label: "Phase 1"},
{period: "F2", label: "Phase 2"},
{period: "F3", label: "Phase 3"},
{period: "MAX", selected: true, label: "MAX"}
];
I need to intercept when user click on period to reload the graph, I see that I can do it with this, from here:
var chart = AmCharts.makeChart( "chartdiv", {
"type": "stock",
// ...
"periodSelector": {
"position": "left",
"periods": [ {
"period": "MM",
"selected": true,
"count": 1,
"label": "1 month"
}, {
"period": "YYYY",
"count": 1,
"label": "1 year"
}, {
"period": "YTD",
"label": "YTD"
}, {
"period": "MAX",
"label": "MAX"
} ],
"listeners": [ {
"event": "changed",
"method": function( event ) {
if ( event.predefinedPeriod !== undefined ) {
console.log( event.predefinedPeriod, event.count );
}
}
} ]
},
// ...
} );
But I don't know how to put the last listeners part and use it with my code.
thanks
You can either use the addListener method or just set the listeners array directly on your periodSelectorAjax variable.
addListener example:
periodSelectorAjax.addListener("changed", function(event) {
// your code here
});
listeners property example:
periodSelectorAjax.listeners = [{
"event": "changed",
"method": function(event) {
// ...
}
}]

Resources