I am building an add-in for Outlook, using Office.JS.
My add-in retrieves the (signed-in) user's contacts.
I am trying to achieve the following:
After the contacts are retrieved, when the user is in compose mode, add the loaded contacts to the recipient's field.
It should only be added for the period of creating the message (I don't want to add it permanently to the user's contact list),
Thanks
I have not found a proper way yet, according to the documentation, that allows achieving this.
when the user is in compose mode, add the loaded contacts to the recipient's field. It should only be added for the period of creating the message (I don't want to add it permanently to the user's contact list)
The Office JavaScript API provides asynchronous methods (Recipients.getAsync, Recipients.setAsync, or Recipients.addAsync) to respectively get, set, or add recipients in a compose form of an appointment or message. These asynchronous methods are available to only compose add-ins. For example, to set recipients for your message you can use the following code:
let item;
Office.initialize = function () {
item = Office.context.mailbox.item;
// Checks for the DOM to load using the jQuery ready method.
$(document).ready(function () {
// After the DOM is loaded, app-specific code can run.
// Set recipients of the composed item.
setRecipients();
});
}
// Set the display name and email addresses of the recipients of
// the composed item.
function setRecipients() {
// Local objects to point to recipients of either
// the appointment or message that is being composed.
// bccRecipients applies to only messages, not appointments.
let toRecipients, ccRecipients, bccRecipients;
// Verify if the composed item is an appointment or message.
if (item.itemType == Office.MailboxEnums.ItemType.Appointment) {
toRecipients = item.requiredAttendees;
ccRecipients = item.optionalAttendees;
}
else {
toRecipients = item.to;
ccRecipients = item.cc;
bccRecipients = item.bcc;
}
// Use asynchronous method setAsync to set each type of recipients
// of the composed item. Each time, this example passes a set of
// names and email addresses to set, and an anonymous
// callback function that doesn't take any parameters.
toRecipients.setAsync(
[{
"displayName":"Graham Durkin",
"emailAddress":"graham#contoso.com"
},
{
"displayName" : "Donnie Weinberg",
"emailAddress" : "donnie#contoso.com"
}],
function (asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed){
write(asyncResult.error.message);
}
else {
// Async call to set to-recipients of the item completed.
}
}); // End to setAsync.
// Set any cc-recipients.
ccRecipients.setAsync(
[{
"displayName":"Perry Horning",
"emailAddress":"perry#contoso.com"
},
{
"displayName" : "Guy Montenegro",
"emailAddress" : "guy#contoso.com"
}],
function (asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed){
write(asyncResult.error.message);
}
else {
// Async call to set cc-recipients of the item completed.
}
}); // End cc setAsync.
// If the item has the bcc field, i.e., item is message,
// set bcc-recipients.
if (bccRecipients) {
bccRecipients.setAsync(
[{
"displayName":"Lewis Cate",
"emailAddress":"lewis#contoso.com"
},
{
"displayName" : "Francisco Stitt",
"emailAddress" : "francisco#contoso.com"
}],
function (asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed){
write(asyncResult.error.message);
}
else {
// Async call to set bcc-recipients of the item completed.
// Do whatever appropriate for your scenario.
}
}); // End bcc setAsync.
}
}
// Writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
And if you need to add recipients:
// Add specified recipients as required attendees of
// the composed appointment.
function addAttendees() {
if (item.itemType == Office.MailboxEnums.ItemType.Appointment) {
item.requiredAttendees.addAsync(
[{
"displayName":"Kristie Jensen",
"emailAddress":"kristie#contoso.com"
},
{
"displayName" : "Pansy Valenzuela",
"emailAddress" : "pansy#contoso.com"
}],
function (asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed){
write(asyncResult.error.message);
}
else {
// Async call to add attendees completed.
// Do whatever appropriate for your scenario.
}
}); // End addAsync.
}
}
These operations are not temporary, the changes made will be saved to the message.
Related
I have created a post back dialog but trying to post some arguments while calling the postback dialog in microsoft bot framework in v#4 doesn't work as expected.
I have written this code to call the post back dialog from hero card. Here i am able to call the post back but i'm not able to send values(arguments) to postback dialog.
var card1 = CardFactory.heroCard(
Name,
Address1+", "City",
CardFactory.images([Image]),
CardFactory.actions([
{
type: 'postBack',
title: Service,
value: PostBack_DIALOG,
text: 'arguments for postback dialog'
},
{
type: 'call',
title: phone ,
value: "tel:" + phoneNumber
}
])
Please suggest how to send the arguments to post back dialog in bot framework v#4
You can achieve this via sending the argument, not as part of the card, but as part of a successive sendActivities step once the hero card has rendered and the user has selected a button.
In the following code example, I have two steps. One for rendering the card and one for sending the data after the user has interacted with the card. In the first step, called postBackCardStep, I am capturing a previous value from a suggested action where the user selected either 'blue' or 'green'. That value is then passed in the Service button when that button is pressed.
In the second step, called postBackPostStep, I capture and send the user's selection in resultDetails. I also define a property, called postBackArg, and send a property vlaue for validation on the client side. I send this in a data dictionary object in a sendActivities activity. This allows me to send, as one action, both a message and the data.
async postBackCardStep ( stepContext ) {
const PostBack_DIALOG = stepContext.context.activity.text;
const reply = { type: ActivityTypes.Message };
const postBackBtn = { type: ActionTypes.PostBack, title: 'Service', value: PostBack_DIALOG }
const callBtn = { type: ActionTypes.Call, title: 'Phone', value: '123-456-7890' }
const card = CardFactory.heroCard(
'<Name>',
'<Address>',
['<link to some image>'],
[
postBackBtn,
callBtn
]
);
reply.attachments = [ card ];
await stepContext.context.sendActivity( reply );
return { status: DialogTurnStatus.waiting };
}
async postBackPostStep ( stepContext ) {
const resultDetails = stepContext.context.activity.text;
const data = { value: resultDetails, postBackArg: 'true' };
await stepContext.context.sendActivities( [
{ text: `Sending ${ resultDetails } via postBack`, type: 'message' },
{ name: 'data', type: 'event', channelData: { 'data': data } }
] );
return await stepContext.next();
}
Next, in the web chat script in the html page, I have the following code. I create a store, create and name an event, and pass the acquired data from the bot into that event. I then create an event listener that validates the data by checking if the received data is an event type and a postBackArg value of 'true' (this value should be passed from the bot as a string and validated as a string). If both checks pass, then the console message is posted with the values. The store is then passed into renderWebChat.
const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => action => {
if ( action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' ) {
const event = new Event('incomingActivity');
event.data = action.payload.activity;
window.dispatchEvent(event);
}
return next( action );
} );
window.addEventListener('incomingActivity', ({ data }) => {
const type = data.type;
if (type === 'event' && data.channelData.data.postBackArg === 'true') {
console.log('DATA ', data);
const resultDetails = data.channelData.data.value;
const args = data.channelData.data.postBackArg;
console.log(`Received a message from the bot (${resultDetails}) with argument value ${args}.`);
}
} );
window.WebChat.renderWebChat( {
directLine: directLine,
store
}, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
Here, you can see the data has successfully been passed from the bot to the page which, as specified in the incomingActivity event listener, only occurs if the correct data properties were passed and successfully validated.
Unless I'm much mistaken, at this point you should be good to go!
Hope of help!
I have eventbus that is fired when a button is clicked, the challenge is that the code inside the callback is not working but the console logging inside same callback works.
This is how I'm firing the event.
editCampaign (campaign) {
eventBus.$emit('editCampaign', campaign);
this.$router.push('/campaign/create');
}
And below is how I'm listening to the event.
created() {
this.getDetails();
eventBus.$on('deleted-message', (messages) => {
this.messageDeleted(messages)
});
let self = this;
eventBus.$on('editCampaign', function(campaign) {
console.log('campaign edited');
self.edit = true;
self.campaign = campaign
})
}
It console logged campaign edited but the edit and campaign were not updated.
data() {
return {
campaign: '',
edit: false
}
},
I have a backbone view where I call model.save to create/updated date submitted in the form. Before calling the save I explicitly call model.isValid(true) to validate the form fields then I process the form data to make it ready for API expected format (by adding or modifying additional fields) and then make call to mode.save function which is again triggering validate function where the validations are getting failed due to the modified data. As I have already called the isValid function explicitly, I want to prevent the call again during save. How can I do it in backbone. Here is sample code.
var data = Backbone.Syphon.serialize($(e.currentTarget).closest('form.my_form')[0]));
this.model.set(data);
if(this.model.isValid(true)) {
data['metas'] = this.context.metaData;
data['metas'][0]['locale'] = this.parentObj.model.get('locale');
data['metas'][0]['name'] = data['name'];
delete data['name'];
}
var tempDynAttrs = [];
if(data['dynamicAttributes']){
$.each(data['dynamicAttributes'], function(index,obj) {
if(obj['attributeValue'] !== null && obj['attributeValue'] !== undefined ) {
tempDynAttrs.push({
attributeName: obj['attributeName'],
attributeValue: [obj['attributeValue']],
locale: data['defaultLocale'],
status: 'active'
});
}
});
}
data['dynamicAttributes'] = tempDynAttrs;
this.model.save(data, {
url: this.model.url(),
patch: true,
success : function(model, response) {
$('#headerMessage').html('Data is updated successfully');
},
error : function(model, response) {
$('#headerMessage').html('Error updating data');
}
});
} else {
$('#formPanel').animate({
scrollTop: $('.has-error').first().offset().top-50
}, 100);
return false;
}
Try passing {validate:false} in the save options, like
book.save({author: "Teddy"}, {validate:false});
According to change log of version 0.9.10:
Model validation is now only enforced by default in Model#save and no longer enforced by default upon construction or in Model#set, unless the {validate:true} option is passed.
So passing {validate:false} should do the trick.
I have created APIs with Nodejs/Express.
Let say I can do GET request to localhost:8080/list and it returns JSON of my TODO list and I can POST to localhost:8080/list to create new to do list.
Then I use Riotjs + Riotcontrol for my Frontend website.
How do I request from todostore.js file?
This is the riotcontrol todostore.js file which I get from riotcontrol demo folder
Riotcontrol
// TodoStore definition.
// Flux stores house application logic and state that relate to a specific domain.
// In this case, a list of todo items.
function TodoStore() {
riot.observable(this) // Riot provides our event emitter.
var self = this
self.todos = [
{ title: 'Task 1', done: false },
{ title: 'Task 2', done: false }
]
// Our store's event handlers / API.
// This is where we would use AJAX calls to interface with the server.
// Any number of views can emit actions/events without knowing the specifics of the back-end.
// This store can easily be swapped for another, while the view components remain untouched.
self.on('todo_add', function(newTodo) {
self.todos.push(newTodo)
self.trigger('todos_changed', self.todos)
})
self.on('todo_remove', function() {
self.todos.pop()
self.trigger('todos_changed', self.todos)
})
self.on('todo_init', function() {
self.trigger('todos_changed', self.todos)
})
// The store emits change events to any listening views, so that they may react and redraw themselves.
}
You could do something like this in your TodoStore
self.on('todo_init', function() {
// Trigger loading here perhaps, then set loading = false when it's loaded
//self.trigger('set_loading', {value: true})
fetch('http://localhost:8080/list')
.then(response => response.json())
.then(function (json) {
self.todos = json
self.trigger('todos_changed', self.todos)
})
})
I am trying to implement a custom validator for a paper-input. In this particular case, the control should accept positive numbers. However, not only only will the control only accept positive numbers, it will also run some other custom validation logic to determine if the entry falls within a constantly changing (dynamic & calculated) upper and lower limit. Ideally, the paper-input control's error-message text will also change depending on what part of the custom validator check failed.
In the past, I was able to implement this sort of thing with the gold-email-input element. In that case, the control checks for an entry that matches a regular expression for email addresses (i.e. implements a type-check). It also calls a backend api to see if the email address entered (as it is being typed), already exists in a database. If it exists in the database, the control fails validation and updates the control's validation error-message with a custom message. If it does not exist, it passes validation. As you might have imagined by this description, this was for a user registration UI element whereby the provided email should not already exist in the current list of user accounts. Here is an excerpt of that working code below for your reference:
<gold-email-input id="userEmail" label="Email" required auto-validate value="{{userEmail}}" error-message$="{{_getEmailErrorMsg(0)}}" invalid="{{_emailInvalid}}" validator="_validateEmail"></gold-email-input>
<iron-signals on-iron-signal-email-used="_accountFound" on-iron-signal-email-available="_accountNotFound"></iron-signals>
<script>
var emailErrors = ["Provide a valid email address", "Address already used"];
// Register the polymer element
Polymer({
properties: {
userEmail: {type: String, value: null},
validated: {type: Boolean, notify: true}, //overall validity state of entire element
_emailInvalid: {type: Boolean, value: true, observer: "_validityChanged"}, // validity state of email input itself
},
ready: function() {
// Called before attached
this.$.userEmail.validate = this._validateEmail.bind(this);
},
_accountFound: function() {
// Listener function intended to fire when the user email address/account was found
console.log(this.nodeName + " accountFound listener called\n");
this.$.userEmail.errorMessage = this._getEmailErrorMsg(1);
this._emailInvalid = true;
},
_accountNotFound: function() {
// Listener function intended to fire when the user email address/account was not found
console.log(this.nodeName + " accountNotFound listener called\n");
this.$.userEmail.errorMessage = this._getEmailErrorMsg(0);
this._emailInvalid = false;
},
_checkAccountExistance: function() {
if (this.userEmail !== undefined && this.userEmail != null) {
this.$.user.checkEmailAvailability(this.userEmail);
} else {
this._emailInvalid = true;
}
},
_getEmailErrorMsg: function(code) {
if (code !== undefined && code != null) {
return emailErrors[code];
} else {
return "";
}
},
_validateEmail: function() {
// Custom validator function for email input (also checks if email has already been associated to any user accounts)
console.log(this.nodeName + " validateEmail validator called\n");
// Check if proper email address format (W3C Spec Regex used)
var validEntry = /^[a-zA-Z0-9.!#$%&�*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(this.userEmail);
if (validEntry) {
this._emailInvalid = false;
this._checkAccountExistance();
} else {
this._emailInvalid = true;
}
}
_validityChanged: function(newVal, oldVal) {
// set the containing/parent element's overall validity state
this.validated = (!this._nameInvalid && !this._pwInvalid && !this._phoneInvalid && !this._emailInvalid && !this._countryInvalid && !this._regionInvalid && !this._cityInvalid);
},
});
</script>
Now, if I try to implement a similar approach with the paper-input component, it does not work. The custom validator function does not get called at any point. Is there something inherently different with paper-input compared to gold-email-input? Should it not treat validation the same way?
<paper-input id="xpos" label="Horizontal Position" required auto-validate value="{{XPos}}" error-message="Provide the x position" invalid="{{_xInvalid}}" validator="_validatePosition"></paper-input>
<script>
// Register the polymer element
Polymer({
properties: {
xPos: {type: Number},
validated: {type: Boolean, notify: true}, //overall validity state of entire element
_xInvalid: {type: Boolean, value: true, observer: "_validityChanged"}, // validity state of xpos input itself
},
ready: function() {
// Called before attached
this.$.xpos.validate = this._validatePosition.bind(this);
},
_validatePosition: function() {
console.log(this.nodeName + " validatePosition validator called\n");
// perform some validation code here like the gold-email-input example above
}
});
</script>