Autocomplete in Google Apps Script with data from Google Sheets - javascript

I am trying to use Google Apps Script to create a form, where the fields allow for autocompletion. In other forms I’ve created, I’ve been able to pull an array of options from a google sheet, and use them to populate a drop down list, so I have to think it’s possible to do the same with an autocomplete process.
I’ve blatantly copied this example from w3schools, and it works exactly as needed, as long as I declare the array within the javascript (as done in the example). But what I haven’t been able to figure out is how to populate the array with options pulled from my google sheet.
Here is where I started:
var PMOSupport;
$(function() {
google.script.run.withSuccessHandler(buildDropDowns)
.getPMOSupport();
});
function buildDropDowns(data) {
PMOSupport = data;
console.log(PMOSupport);
}
function autocomplete(inp, arr) {
console.log("ENTER AUTO");
var currentFocus;
inp.addEventListener("input", function(e) {
// all of the remaining code is direct from the w3schools example
// I'm cutting it from here for brevity,
// and because I know this works, when using the declared array below
});
}
var countries = ["Afghanistan","Albania","Algeria","Andorra"];
// this line works fine, when using the array declared above
// autocomplete(document.getElementById("myInput"), countries);
// this line does not work, when trying to use the array populated from the google sheet
autocomplete(document.getElementById("myInput"), PMOSupport);
When I run this, the page creates, and as soon as I type into the entry field, I get a message in the console:
`Uncaught TypeError: Cannot read property 'length' of undefined`
at HTMLInputElement.<anonymous> (<anonymous>:32:28)
When I look into this, it’s saying that the ‘arr’ argument (PMOSupport) isn’t populated. That’s why I added the 2 console.log lines, to see what order things are happening. When I open the page, “ENTER AUTO” logs first, then the State Changes from Idle to Busy and Busy to Idle (while it calls getPMOSupport()), then the PMOSupport array logs in the console (also proving that I am in fact getting the correct data back from the sheet). So clearly, it’s entering function autocomplete() before it calls the google.script.run.withSuccessHandler(buildDropDowns).getPMOSupport() portion, which is why the 'arr' argument is undefined.
I’ve tried various ways of taking that out of the $(function() … }); block, to try to get the PMOSupport array populated before the autocomplete() function runs. Nothing I’ve done seems to be working.
I’m sure this is something simple, and caused by bad habits I’ve picked up over time (I’m not a developer, I just cobble things together for my team). But any help would be appreciated.

You need to call autocomplete AFTER the asynchronous code has returned. So, you need to invoke it from the callback.
function buildDropdowns(data, userObject) {
// probably you should indicate in data which field these data is for, or use
// the userObject parameter in the google.script.run API
autocomplete(document.getElementById("myInput"), data);
}
Alternately (I haven't and won't look at the w3schools code), declare your PMOSupport as a const array initially, and then add the entries from your callback into it (instead of reassigning it). This way, the variable is not undefined, it is just empty at the start. Depending on the implementation of the autocomplete code, this may or may not work for you.
const PMOSupport = [];
....
function buildDropdowns(data) {
PMOSupport.push(...data);
// or
// Array.prototype.push.apply(PMOSupport, data);
}

Related

Custom and first options in ember-power-select

I'm using Ember-power-select-with-create to make custom selections possible in my dropdowns.
This is the power-select part in the .hbs file.
{{#power-select-multiple-with-create
options=options //array for prefill values
selected=offer.offer //is where we save it to (offer is the model and has an array named offer)
onchange=(action (mut offer.offer)) //default onchange for prefill options
onfocus=(action "handleFocus")
onblur=(action "handleBlur")
oncreate=(action "createOffer" offer.offer) //This calls "createOffer"
buildSuggestion=suggestion
as |offer|}}
{{offer}}
{{/power-select-multiple-with-create}}
The "createOffer" function in my .js file looks like this:
It gets two values passed: selected which is offer.offer and searchText which is the input that the user is trying to add.
createOffer(selected, searchText)
{
if (selected == null)
{
selected = []; //This is what I'm currently trying: if its null initialize it
}
if (selected.includes(searchText)) { //Don't add if it's already added
this.get('notify').alert("Already added!", { closeAfter: 4000 });
}
else { // if it's not yet added push it to the array
selected.pushObject(searchText);
}
}
This code works perfectly fine for adding those that we predefined as well for custom options, however it does not work if it is the first and a custom offer that we try to add to a new group of offers.
I'm assuming it has something to do with the fact that the array is not yet initialized. A pointer to the fact that it is due to the array not being initialized is that I get an error message along the lines of: Can't call pushObject on undefined. And I'm assuming the reason that it works with predetermined values is that ember-power-select probably initializes it somewhere but I couldn't find any documentation about it.
Thanks for your answers!
If anyone ever stumbles upon this, this was the solution
Calling that when we create the offer and then initializing the offer array like that:
offer.set('offer', []);

Running client side javascript without loading a new (blank) view on Odoo 8

I need to run some client-side javascript from a button in a form view in Odoo 8. This button runs a python method which returns this dictionary:
{"type": "ir.actions.client",
"tag": "my_module.do_something",}
do_something is defined in a .js file as follows:
openerp.my_module = function (instance) {
instance.web.client_actions.add("my_module.do_something", "instance.my_module.Action");
instance.my_module.Action = instance.web.Widget.extend({
init: function() {
// Do a lot of nice things here
}
});
};
Now, the javascript is loaded and executed properly, but even before launching the init function, Odoo loads a brand new, blank view, and once the javascript is over I can't get browse any other menu entry. In fact, wherever I click I get this error:
Uncaught TypeError: Cannot read property 'callbackList' of undefined
What I need instead is to run the javascript from the form view where the button belongs, without loading a new view, so both getting the javascript stuff done and leaving all callbacks and the whole environment in a good state. My gut feeling is that I shouldn't override the init funcion (or maybe the whole thing is broken, I'm quite new to Odoo client-side js) , but I couldn't find docs neither a good example to call js the way I want. Any idea to get that?
Sorry, I don't work on v8 since a lot of time and I don't remember how to add that, but this might help you: https://github.com/odoo/odoo/blob/8.0/doc/howtos/web.rst
Plus, if you search into v8 code base you can find some occurence of client actions in web module docs https://github.com/odoo/odoo/search?utf8=%E2%9C%93&q=instance.web.client_actions.add
Thanks to the pointers simahawk posted in another answer, I have been able to fix my js, which is now doing exactly what I needed. For your reference, the code is as follows:
openerp.my_module = function (instance) {
instance.web.client_actions.add("my_module.do_something", "instance.my_module.action");
instance.my_module.action = function (parent, action) {
// Do a lot of nice things here
}
};

Is there a way to log the JavaScript stack trace without putting breakpoints?

I would like to be able to perform manipulations on a given JS app, and then simply get a large log of all the functions that have been called. This is possible in Chrome, but only if one puts a breakpoint somewhere. My problem is when I am reverse-engineering a given website (only for self-teaching purposes, of course) it often takes me a whole lot of time to figure out where to even start from. Something like that will help me tremendously because I will no longer have to search within the code, rather, I will just do a user action, and grab the stack log afterwards.
I suppose that there should be a way to intercept (or wrap) every function call, so that it is dumped to the log before the function is called.
Try this article:
http://eriwen.com/javascript/stacktrace-update/
or this post:
http://ivan-ghandhi.livejournal.com/942493.html
and, probably, this: How can I get a Javascript stack trace when I throw an exception?
In Firebug, you can use the profiler to log every function called. Use console.profile() and console.profileEnd() to trigger it programatically.
However, this will not give you proper stack traces. (Are you sure that's what you want?)
To log methods of specific objects, you can overwrite them like so:
for (var key in obj) {
if (typeof obj[key] == 'function') {
(function(){
var origFun = obj[key];
obj[key] = function () {
var result = origFun.apply(this, arguments);
console.log('call to method', key, 'with arguments', arguments,' - Result:', result);
// console.trace(); // for a trace with every call
return result;
};
})();
}
}
Maybe aspect oriented programming (AOP) can provide an answer. I just found out about aspectJS which could help intercept and log function calls
You can use dynatrace. Dynatrace is a profiling tool for IE and FF. Dynatrace can monitor your application while it is running, and then serves you a timeline of all what happened. In the timeline, there is blocks representing the javascript activity. You can right-click on it (purepath), and then walk through the whole call stack. You can export that to excel or other If you want.
You can add markers in your code, those markers will appear on the timeline and in the purepath:
if(typeof(_dt_addMark)!="undefined") _dt_addMark('MyCustomTimerName');
alternatively, if you only want to find "a way to intercept (or wrap) every function call",
there is a low-tech solution, if you are using a real webbapp (single-load javascript app):
bookmarklets
With bookmarklets, once you have loaded your page, you can execute some custom javascript. So what you can do there, is to override the functions methods that you want to observe with the same function containing logging (so just copy and paste the function, and add some console.log in there). This actually works even with native js functions.

Adding a record to the store

I have a editable grid, store and a button.
The button has a handler that is supposed to copy the selected record and add the copy to the store:
var a = gridPanel.getSelectionModel().getSelectedCell();
var rec = store.getAt(a[0]).copy();
store.addSorted(rec);
alert (store.getAt(1).get('date'));
But the store and the grid are not updated. The alert has an error - cannot call method of undefined.
What is the problem here?
The issue probably is that the copied record has the same id thus when you insert it in the store another record with the same id is already present.
If you generate and apply a new id to the copied record before adding it to the store it should work. The following code generates a new ID in the record passed as argument. Check the documentation of Ext.data.Record.copy.
Ext.data.Record.id(rec);
Few things that most JavaScript developers should do:
Use firebug, if you turn on break on all errors it would probably show you that store.get(1) is returning undefing and causing an error when you try to call a function on an undefined.
Now that you have firebug use console.log() statements over window.alert(). With console.log's you can actually see the an inspect it, it is also good for asynchronous stuff and mouse events.
As for your problem:
Calling a record.copy() and then inserting it into the store will cause problems if you don't give it an id. If you had firebug and looked through the code you would stumble upon this:
if(this.containsKey(key)){
this.suspendEvents();
this.removeKey(key);
this.resumeEvents();
}
To generate a record with a unique key you can do something like this :
var rec = store.getAt(a[0]).copy();
var id = Ext.data.Record.id(id);
rec.id = id;
The code seems messy but there aren't many nice ways of doing it. If it were me I would override the copy function to take a boolean to force the record's id to be auto generated.

Google Website Optimiser Control Javascript

Could someone explain the javascript that makes up Google's Website Optimiser Control script? Specifically: the first two lines, which seem to be empty functions, and why is the third function wrapped parentheses () ?
As far as I can tell this script is basically writing out a new <script> which presumably loads something for A/B testing.
function utmx_section(){}
function utmx(){}
(function() {
var k='0634742331',d=document,l=d.location,c=d.cookie;
function f(n) {
if(c) {
var i=c.indexOf(n+'=');
if (i>-1) {
var j=c.indexOf(';',i);
return escape(c.substring(i+n.length+1,j<0?c.length:j))
}
}
}
var x=f('__utmx'),xx=f('__utmxx'),h=l.hash;
d.write('<sc'+'ript src="'+'http'+(l.protocol=='https:'?'s://ssl':'://www')+'.google-analytics.com'+'/siteopt.js?v=1&utmxkey='+k+'&utmx='+(x?x:'')+'&utmxx='+(xx?xx:'')+'&utmxtime='+new Date().valueOf()+(h?'&utmxhash='+escape(h.substr(1)):'')+'" type="text/javascript" charset="utf-8"></sc'+'ript>')
}
)();
I've attempted to step through with the firebug debugger but it doesn't seem to like it. Any insights much appreciated.
Many thanks
inside anonymous function it shortens names of document and cookies inside it at first, function f(n) gets value of cookie under name n. Then Google reads its cookies and with help of d.write it loads its scripts (as I see they are related to Google Analytic). This way it makes On-Demand JavaScript loading... Actually you load these scripts all the time, Google just needs some additional parameters in url, so this is done this way - save parameters in cookie, which next time are used to get script again.
And finally back to the first two magic lines :) After Google loads its script (after executing d.write), there are some functions which uses utmx and utmx_section, as well as definition of these functions, or better to say overriding. I think they are empty at first just because another function can execute it before its real definition, and having empty functions nothing will happen (and no JS error), otherwise script would not work. E.g. after first iteration there is some data, which is used to make real definition of these functions and everything starts to work :)
The first 2 functions are in fact empty, and are probably overridden later on.
The third function is an anonymous self-executing function. The brackets are a convention to make you aware of the fact that it is self executing.
the "f" function looks up the value given to it in the document's cookies and returns it. Then a new script tag is written to document (and requested from server) with these values as part of its URL.

Categories

Resources