Meteor Tabular not reacting to ReactiveDict's values changing - node.js

I'm using the great Tabular package. https://github.com/Meteor-Community-Packages/meteor-tabular.
I'm making use of the client-side selector helper to Reactively change my table by having Server modify the query for my dataset.
I have multiple HTML inputs that act as filters and am populating a ReactiveDict with the values. A search-button click event triggers the ReactiveDict to get populated with an Object using .set
Initialization of ReactiveDict
Template.tbl.onCreated(function () {
const self = this;
self.filters = new ReactiveDict({});
});
Population of ReactiveDict
'click #search-button'(e, template) {
//clear to 'reset' fields in ReactiveDict that could've been cleared by User
template.filters.clear();
const searchableFields = getSearchableFields();
//Initialize standard JS Obj that ReactiveDict will then be set to
const filterObj = {};
//Loop through search fields on DOM and populate into Obj if they have a val
for (let field of searchableFields) {
const value = $(`#${field}-filter`).val();
if (value) {
filterObj[field] = new RegExp(escapeStringRegex(value.trim()), 'i');
}
}
if (Object.keys(filterObj).length) {
template.filters.set(filterObj);
}
},
Selector Helper
selector: () => {
const filters = Template.instance().filters.all();
const selector = { SOME_DEFAULT_OBJ, ...filters };
return selector;
},
I'm noticing the server doesn't notice any changes from a ReactiveDict if all keys remain the same.
I'm testing this by logging in the serve-side's changeSelector md and verifying that my logging does not occur if just a value in selector has changed.
Is there a solution to this?
I.e. {foo:'foo'} to {foo:'bar'} should reactively trigger the server to re-query but it does not. But {foo:'foo'} to {bar:'bar'} would get triggered.
Is this an issue with how I'm using the ReactiveDict or is this on the Tabular side?
Thanks

Related

setting context with list of objects as prameters in dialogflow

I have a list of values each having another KEY value corresponding to it, when i present this list to user, user has to select a value and agent has to call an external api with selected value's KEY. how can i achieve this in dialogflow?
I tried to send the entire key value pair in the context and access it in the next intent but for some reason when i set a list(array) to context parameters dialogflow simply ignoring the fulfillment response.
What is happening here and is there any good way to achieve this? I am trying to develop a food ordering chatbot where the category of items in menu is presented and list items in that menu will fetched when user selects a category, this menu is not static thats why i am using api calls to get the dynamic menu.
function newOrder(agent)
{
var categories = []
var cat_parameters = {}
var catarray = []
const conv = agent.conv();
//conv.ask('sure, select a category to order');
agent.add('select a category to order');
return getAllCategories().then((result)=>{
for(let i=0; i< result.restuarantMenuList.length; i++)
{
try{
var name = result.restuarantMenuList[i].Name;
var catid = result.restuarantMenuList[i].Id;
categories.push(name)
//categories.name = catid
cat_parameters['id'] = catid;
cat_parameters['name'] = name
catarray.push(cat_parameters)
}catch(ex)
{
agent.add('trouble getting the list please try again later')
}
}
agent.context.set({
name: 'categorynames',
lifespan: 5,
parameters: catarray, // if i omit this line, the reponse is the fultillment response with categories names, if i keep this line the reponse is fetching from default static console one.
})
return agent.add('\n'+categories.toString())
})
function selectedCategory(agent)
{
//agent.add('category items should be fetched and displayed here');
var cat = agent.parameters.category
const categories = agent.context.get('categorynames')
const cat_ob = categories.parameters.cat_parameters
// use the key in the catarray with the parameter cat to call the external API
agent.add('you have selected '+ cat );
}
}
The primary issue is that the context parameters must be an object, it cannot be an array.
So when you save it, you can do something like
parameters: {
"cat_parameters": catarray
}
and when you deal with it when you get the reply, you can get the array back with
let catarray = categories.parameters.cat_parameters;
(There are some other syntax and scoping issues with your code, but this seems like it is the data availability issue you're having.)

React Native: Reach-Navigation and Pouch-DB - db.put not done before "refresh" callback is run

Relative newbie; forgive me if my etiquette and form here aren't great. I'm open to feedback.
I have used create-react-native-app to create an application using PouchDB (which I believe ultimately uses AsyncStorage) to store a list of "items" (basically).
Within a TabNavigator (main app) I have a StackNavigator ("List screen") for the relevant portion of the app. It looks to the DB and queries for the items and then I .map() over each returned record to generate custom ListView-like components dynamically. If there are no records, it alternately displays a prompt telling the user so. In either case, there is an "Add Item" TouchableOpacity that takes them to a screen where they an add a new item (for which they are taken to an "Add" screen).
When navigating back from the "Add" screen I'm using a pattern discussed quite a bit here on SO in which I've passed a "refresh" function as a navigation param. Once the user uses a button on the "Add" screen to "save" the changes, it then does a db.post() and adds them item, runs the "refresh" function on the "List screen" and then navigates back like so:
<TouchableOpacity
style={styles.myButton}
onPress={() => {
if (this.state.itemBrand == '') {
Alert.alert(
'Missing Information',
'Please be sure to select a Brand',
[
{text: 'OK', onPress: () =>
console.log('OK pressed on AddItemScreen')},
],
{ cancelable: false }
)
} else {
this.createItem();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsScreen');
}
}
}
>
And all of this works fine. The "refresh" function (passed as onGoBack param) works fine... for this screen. The database is called with the query, the new entry is found and the components for the item renders up like a charm.
Each of the rendered ListItem-like components on the "List screen" contains a react-native-slideout with an "Edit" option. An onPress for these will send the user to an "Item Details" screen, and the selected item's _id from PouchDB is passed as a prop to the "Item Details" screen where loadItem() runs in componentDidMount and does a db.get(id) in the database module. Additional details are shown from a list of "events" property for that _id (which are objects, in an array) which render out into another bunch of ListItem-like components.
The problem arises when either choose to "Add" an event to the list for the item... or Delete it (using another function via [another] slideout for these items. There is a similar backward navigation, called in the same form as above after either of the two functions is called from the "Add Event" screen, this being the "Add" example:
async createEvent() {
var eventData = {
eventName: this.state.eventName.trim(),
eventSponsor: this.state.eventSponsor.trim(),
eventDate: this.state.eventDate,
eventJudge: this.state.eventJudge.trim(),
eventStandings: this.state.eventStandings.trim(),
eventPointsEarned: parseInt(this.state.eventPointsEarned.trim()),
};
var key = this.key;
var rev = this.rev;
await db.createEvent(key, rev, eventData);
}
which calls my "db_ops" module function:
exports.createEvent = function (id, rev, eventData) {
console.log('You called db.createEvent()');
db.get(id)
.then(function(doc) {
var arrWork = doc.events; //assign array of events to working variable
console.log('arrWork is first assigned: ' + arrWork);
arrWork.push(eventData);
console.log('then, arrWork was pushed and became: ' + arrWork);
var arrEvents = arrWork.sort((a,b)=>{
var dateA = new Date(a.eventDate), dateB = new Date(b.eventDate);
return b.eventDate - a.eventDate;
})
doc.events = arrEvents;
return db.put(doc);
})
.then((response) => {
console.log("db.createEvent() response was:\n" +
JSON.stringify(response));
})
.catch(function(err){
console.log("Error in db.createEvent():\n" + err);
});
}
After which the "Add Event" screen's button fires the above in similar sequence to the first, just before navigating back:
this.createEvent();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsDetails');
The "refresh" function looks like so (also called in componentDidMount):
loadItem() {
console.log('Someone called loadItem() with this.itemID of ' + this.itemID);
var id = this.itemID;
let totalWon = 0;
db.loadItem(id)
.then((item) => {
console.log('[LOAD ITEM] got back data of:\n' + JSON.stringify(item));
this.setState({objItem: item, events: item.events});
if (this.state.events.length != 0) { this.setState({itemLoaded: true});
this.state.events.map(function(event) {
totalWon += parseInt(event.eventPointsEarned);
console.log('totalWon is ' + totalWon + ' with ' +
event.eventPointsEarned + ' having been added.');
});
};
this.setState({totalWon: totalWon});
})
.catch((err) => {
console.log('db.loadItem() error: ' + err);
this.setState({itemLoaded: false});
});
}
I'm at a loss for why the List Screen refreshes when I add an item... but not when I'm doing other async db operations with PouchDB in what I think is similar fashion to modify the object containing the "event" information and then heading back to the Item Details screen.
Am I screwing up with Promise chain someplace? Neglecting behavior of the StackNavigator when navigating deeper?
The only other difference being that I'm manipulating the array in the db function in the non-working case, whereas the others I'm merely creating/posting or deleting/removing the record, etc. before going back to update state on the prior screen.
Edit to add, as per comments, going back to "List screen" and the opening "Item Details" does pull the database data and correctly shows that the update was made.
Further checking I've done also revealed that the console.log in createEvent() to print the response to the db call isn't logging until after some of the other dynamic rendering methods are getting called on the "Item Details" screen. So it seems as though the prior screen is doing the get() that loadItem() calls before the Promise chain in createEvent() is resolving. Whether the larger issue is due to state management is still unclear -- though it would make sense in some respects -- to me as this could be happening regardless of whether I've called my onGoBack() function.
Edit/bump: I’ve tried to put async/await to use in various places in both the db_ops module on the db.get() and the component-side loadItem() which calls it. There’s something in the timing of these that just doesn’t jive and I am just totally stuck here. Aside from trying out redux (which I think is overkill in this particular case), any ideas?
There is nothing to do with PDB or navigation, it's about how you manage outer changes in your depending (already mounted in Navigator since they are in history - it's important to understand - so componentDidMount isn't enough) components. If you don't use global state redux-alike management (as I do) the only way to let know depending component that it should update is passing corresponding props and checking if they were changed.
Like so:
//root.js
refreshEvents = ()=> { //pass it to DeleteView via screenProps
this.setState({time2refreshEvents: +new Date()}) //pass time2refreshEvents to EventList via screenProps
}
//DeleteView.js
//delete button...
onPress={db.deleteThing(thingID).then(()=> this.props.screenProps.refreshEvents())}
//EventList.js
...
constructor(props) {
super(props);
this.state = {
events: [],
noEvents: false,
ready: false,
time2refreshEvents: this.props.screenProps.time2refreshEvents,
}
}
static getDerivedStateFromProps(nextProps, currentState) {
if (nextProps.screenProps.time2refreshEvents !== currentState.time2refreshEvents ) {
return {time2refreshEvents : nextProps.screenProps.time2refreshEvents }
} else {
return null
}
}
componentDidMount() {
this._getEvents()
}
componentDidUpdate(prevProps, prevState) {
if (this.state.time2refreshEvents !== prevState.time2refreshEvents) {
this._getEvents()
}
}
_getEvents = ()=> {
//do stuff querying db and updating your list with actual data
}

How to provide custom names for page view events in Azure App Insights?

By default App Insights use page title as event name. Having dynamic page names, like "Order 32424", creates insane amount of event types.
Documentation on the matter says to use trackEvent method, but there are no examples.
appInsights.trackEvent("Edit button clicked", { "Source URL": "http://www.contoso.com/index" })
What is the best approach? It would be perfect to have some sort of map/filter which would allow to modify event name for some pages to the shared name, like "Order 23424" => "Order", at the same time to leave most pages as they are.
You should be able to leverage telemetry initializer approach to replace certain pattern in the event name with the more "common" version of that name.
Here is the example from Application Insights JS SDK GitHub on how to modify pageView's data before it's sent out. With the slight modification you may use it to change event names based on their appearance:
window.appInsights = appInsights;
...
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType) {
// this statement removes url from all page view documents
telemetryItem.url = "URL CENSORED";
}
// To set custom properties:
telemetryItem.properties = telemetryItem.properties || {};
telemetryItem.properties["globalProperty"] = "boo";
// To set custom metrics:
telemetryItem.measurements = telemetryItem.measurements || {};
telemetryItem.measurements["globalMetric"] = 100;
});
});
// end
...
appInsights.trackPageView();
appInsights.trackEvent(...);
With help of Dmitry Matveev I've came with the following final code:
var appInsights = window.appInsights;
if (appInsights && appInsights.queue) {
function adjustPageName(item) {
var name = item.name.replace("AppName", "");
if (name.indexOf("Order") !== -1)
return "Order";
if (name.indexOf("Product") !== -1)
return "Shop";
// And so on...
return name;
}
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType || envelope.name === Microsoft.ApplicationInsights.Telemetry.PageViewPerformance.envelopeType) {
// Do not track admin pages
if (telemetryItem.name.indexOf("Admin") !== -1)
return false;
telemetryItem.name = adjustPageName(telemetryItem);
}
});
});
}
Why this code is important? Because App Insights use page titles by default as Name for PageView, so you would have hundreds and thousands of different events, like "Order 123132" which would make further analysis (funnel, flows, events) meaningless.
Key highlights:
var name = item.name.replace("AppName", ""); If you put your App/Product name in title, you probably want to remove it from you event name, because it would just repeat itself everywhere.
appInsights && appInsights.queue you should check for appInsights.queue because for some reason it can be not defined and it would cause an error.
if (telemetryItem.name.indexOf("Admin") !== -1) return false; returning false will cause event to be not recorded at all. There certain events/pages you most likely do not want to track, like admin part of website.
There are two types of events which use page title as event name: PageView
and PageViewPerformance. It makes sense to modify both of them.
Here's one work-around, if you're using templates to render your /orders/12345 pages:
appInsights.trackPageView({name: TEMPLATE_NAME });
Another option, perhaps better suited for a SPA with react-router:
const Tracker = () => {
let {pathname} = useLocation();
pathname = pathname.replace(/([/]orders[/])([^/]+), "$1*"); // handle /orders/NN/whatever
pathname = pathname.replace(/([/]foo[/]bar[/])([^/]+)(.*)/, "$1*"); // handle /foo/bar/NN/whatever
useEffect(() => {
appInsights.trackPageView({uri: pathname});
}, [pathname]);
return null;
}

How to set multiple values into a client side people picker

i have a list with a people picker column which takes multiple inputs.I have a form created using content editor web part in which i have a client side people picker how to set multiple values into a client side people picker using jsom
Normally you define client people control in HTML:
<div id="control" title="Users" spclientpeoplepicker="true"></div>
Then you need to initialize it.
To initialize it properly you need to assure that these javascripts files are loaded: autofill.js, clienttemplates.js, clientforms.js, clientpeoplepicker.js
JavaScript set control value code:
Entities are validate automatically with function AddUserKeys
var userField = $("input[id$='ClientPeoplePicker_EditorInput']").get(0); // simplified user control search, real word scenario, first search proper row in your form
var peoplepicker = SPClientPeoplePicker.PickerObjectFromSubElement(userField);
peoplepicker.AddUserKeys("Login1"); // or display name
peoplepicker.AddUserKeys("Login2");
JavaScript init control code:
SP.SOD.loadMultiple(['autofill.js', 'clienttemplates.js', 'clientforms.js', 'clientpeoplepicker.js'], registerControls);
function registerControls() {
initPeoplePicker('control');
}
function initPeoplePicker (id, user) {
// Create a schema to store picker properties, and set the properties.
var schema = {};
schema['PrincipalAccountType'] = 'User'; //,DL,SecGroup,SPGroup';
schema['SearchPrincipalSource'] = 15;
schema['ResolvePrincipalSource'] = 15;
schema['AllowMultipleValues'] = true;
schema['MaximumEntitySuggestions'] = 50;
schema['Width'] = '311px';
// use code below to init controls with selected user
/*
var users = new Array(1);
var defaultUser = {};
defaultUser.AutoFillDisplayText = user.Value;
defaultUser.AutoFillKey = user.get_loginName();
defaultUser.Description = user.get_email();
defaultUser.DisplayText = user.get_title();
defaultUser.EntityType = "User";
defaultUser.IsResolved = true;
defaultUser.Key = user.get_loginName();
defaultUser.Resolved = true;
users[0] = defaultUser;
*/
// Render and initialize the picker.
// Pass the ID of the DOM element that contains the picker, an array of initial
// PickerEntity objects to set the picker value, and a schema that defines
// picker properties.
SPClientPeoplePicker_InitStandaloneControlWrapper(id, null/*users*/, schema);
}

Extending or modifying the SharePoint Datasheet view

Has anyone discovered a way to extend or modify the functionality of the SharePoint Datasheet view (the view used when you edit a list in Datasheet mode, the one that looks like a basic Excel worksheet)?
I need to do several things to it, if possible, but I have yet to find a decent non-hackish way to change any functionality in it.
EDIT: An example of what I wish to do is to enable cascading filtering on lookup fields - so a choice in one field limits the available choices in another. There is a method to do this in the standard view form, but the datasheet view is completely seperate.
Regards
Moo
I don't think you can modify it in any non-hackish way, but you can create a new datasheet view from scratch. You do this by creating a new ActiveX control, and exposing it as a COM object, and modifying the web.config file to make reference to the new ActiveX control.
There's an example here:
Creating a custom datasheet control.
Actually, you can do this. Here is a code snippet I stripped out of someplace where I am doing just what you asked. I tried to remove specifics.
var gridFieldOverrideExample = (function (){
function fieldView(ctx){
var val=ctx.CurrentItem[curFieldName];
var spanId=curFieldName+"span"+ctx.CurrentItem.ID;
if (ctx.inGridMode){
handleGridField(ctx, spanId);
}
return "<span id='"+spanId+"'>"+val+"</span>";
}
function handleGridField(ctx, spanID){
window.SP.SOD.executeOrDelayUntilScriptLoaded(function(){
window.SP.GanttControl.WaitForGanttCreation(function (ganttChart){
var gridColumn = null;
var editID = "EDIT_"+curFieldName+"_GRID_FIELD";
var columns = ganttChart.get_Columns();
for(var i=0;i<columns.length;i++){
if(columns[i].columnKey == curFieldName){
gridColumn = columns[i];
break;
}
}
if (gridColumn){
gridColumn.fnGetEditControlName = function(record, fieldKey){
return editID;
};
window.SP.JsGrid.PropertyType.Utils.RegisterEditControl(editID, function (ctx) {
editorInstance = new SP.JsGrid.EditControl.EditBoxEditControl(ctx, null);
editorInstance.NewValue = "";
editorInstance.SetValue = function (value) {
_cellContext = editorInstance.GetCellContext();
_cellContext.SetCurrentValue({ localized: value });
};
editorInstance.Unbind = function () {
//This happens when the grid cell loses focus - hide controls here, do cleanup, etc.
}
//Below I grabbed a reference to the original 'BindToCell' function so I can prepend to it by overwriting the event.
var origbtc = editorInstance.BindToCell;
editorInstance.BindToCell = function(cellContext){
if ((cellContext.record) &&
(cellContext.record.properties) &&
(cellContext.record.properties.ID) &&
(cellContext.record.properties.ID.dataValue)){
editorInstance.ItemID = cellContext.record.properties.ID.dataValue;
}
origbtc(cellContext);
};
//Below I grabbed a reference to the original 'OnBeginEdit' function so I can prepend to it by overwriting the event.
var origbte = editorInstance.OnBeginEdit;
editorInstance.TargetID;
editorInstance.OnBeginEdit = function (cellContext){
this.TargetID = cellContext.target.ID;
/*
. . .
Here is where you would include any custom rendering
. . .
*/
origbte(cellContext);
};
return editorInstance;
}, []);
}
});
},"spgantt.js");
}
return{
fieldView : fieldView
}
})();
(function () {
function OverrideFields(){
var overrideContext = {};
overrideContext.Templates = overrideContext.Templates || {};
overrideContext.Templates.Fields = {
'FieldToOverride' : {
'View': gridFieldOverrideExample.fieldView
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
}
ExecuteOrDelayUntilScriptLoaded(OverrideFields, 'clienttemplates.js');
})();
Also, there are a couple of other examples out there. Sorry, I don't have the links anymore:

Resources