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.
Related
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);
}
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', []);
I have this code:
exports.elevation = functions.database.ref('/ships_for_locations').onCreate(event => {
const original = event.data.val();
console.log(JSON.stringify(original) + " event.data.val() ---------------");
...
The console output for original in the Firebase Functions log is:
{"5eb0fbc824d2d48ce9e5cbc76a7a2e643b016009":{"ship15":{"g":"drt2k6vxuuwy","l":[42.247907072461054,-71.17265374654367]},"ship8":{"l":[42.24786474369262,-71.1727565570968],"g":"drt2k6vxdsf1"},"ship3":{"g":"drt2k6vxg21n","l":[42.247882900393094,-71.17272600000001]},"ship11":{"l":[42.24787704060876,-71.17272600000001],"g":"drt2k6vxer15"},"ship4":{"l":[42.247838578422375,-71.17272600000001],"g":"drt2k6vx7rcj"},"ship16":{"l":[42.24785870234929,-71.17277992860309],"g":"drt2k6vxd58v"},"ship1":{"l":[42.247865,-71.17270322375195],"g":"drt2k6vxeucg"},"ship7":{"l":[42.24787169684869,-71.17270156462881],"g":"drt2k6vxey55"},"ship13":{"l":[42.247842662420545,-71.17268807527044],"g":"drt2k6vxs0tq"},"ship2":{"g":"drt2k6vxdsge","l":[42.247865,-71.17275457694687]},"ship6":{"l":[42.247873403962814,-71.17273898101789],"g":"drt2k6vxdyrm"},"ship12":{"g":"drt2k6vx7m3h","l":[42.247825005009005,-71.17272600000001]},"ship14":{"l":[42.247875702512644,-71.17276722073039],"g":"drt2k6vxdqf7"},"ship9":{"l":[42.247865,-71.17272043195321],"g":"drt2k6vxekv5"},"ship5":{"g":"drt2k6vxkpbw","l":[42.247838767805725,-71.17269442665282]},"ship10":{"l":[42.247865,-71.17276938808422],"g":"drt2k6vxdkbe"}}}
event.data.val() ---------------
However (not matching with the data above), I am adding more than one item to the database reference with my script.
Here is a pastebin of what my database looks like for the ships_for_locations reference after my script runs - https://pastebin.com/Us4we4MU.
You can see there are multiple entries. However, when my onCreate function runs, it only returns one entry (as I showed at the beginning). Did I go over a quota or something? (I looked into it and couldn't find anything). I need all of the entries for /ships_for_locations so I can iterate through them. Thanks
From what you refer to the 0abf655263c3c9fed49387125c265120a4090be8 node holds a single object and this is created once, on the other hand you should have your different ships (ship1, ship2, ... ) directly under ships_for_locations reference with their own ids or ship1, ship2, .. as ids
I changed the reference to /ships_for_locations to /ships_for_locations/{pushId} - and changed my code accordingly.
This is a little hard to describe but here goes. I'm building a page that looks something like the back end to Azure where the user chooses from a menu on the left that loads an item, call it AllUsers for now, to the right of it. From AllUsers there are options for that item and some of those might load another to the left of it, we will call that User. From User you could open more items to the right.
I have it setup nicely with a JS object something like
var AdminPage=function(){
this.con="../controllers/admin/admin_con.php";
this.pageName=null;
this.childPage=null;
this.page=null;
this.deleteItem=function(){
//Recursively deletes pages to the right
if(this.childPage){
this.childPage.delete();
this.childPage=null;
}
if(this.page){
this.page.remove();
}
return this;
}
this.add=function(pageData){
if(this.childPage){
this.deleteItem();
}
this.childPage=new AdminPage();
this.childPage.getPage(pageData);
return this;
};
this.getPage=function(pageData){
var that=this;
this.pageName=pageData.page;
$.post(this.con, pageData,
function(data,status){
if(status==='success'){
$("#rightCol").append(data);
var num=$("#rightCol").children().length;
that.page=$("#rightCol").children()[num-1];
eval(pageData.page+"=that");
}
}
);
return this;
};
}
So each time a page is added it creates a new instance of the object for that item to work with. If that item is removed it recursively removes all items to the right of it as well.
The line "eval(pageData.page+"=that");" and yes I now that eval is evil and have read all about it, creates a new variable name from the name of the item that has been added. That item say the User item then uses that variable when even it needs to call on its instance of the script. This all works mostly perfectly.
Where it fails is if the User item opens another, call it Connections that shows all the connections for that user. I then want to be able to click on a connection and have it open another User item, this works as it should how ever the problem lies when the second User item is opened and creates a new instance of the AdminPage because the same script has just loaded and has the same name it over writes the JS variable from the previous one. Then then affects several things. If you close one of them it closes both for instance and if you close the first that was loaded it should close what is to the right of it however the childPage property is now empty so that doesn't work.
So now for the question..
Is there a better way to handle creating the variables or is there an all around better way all together that will not cause this problem at all?
Can I get around using the Evil eval and make everything right with the world?
Thanks for any suggestions that you may have.
So I have found an answer to the problem but I still don't like it much.
I have generated a unique id and appended it to the end of the name of the script that I was using as the variable.
Issues with this method is it is still using eval and now I have to pass the variable round trip when getting the new item so that I can use php to inject the variable name into the page.
Not perfect and there has to be a better way.
Here is the updated add function
this.add=function(pageData){
if(this.childPage){
this.deleteChildren();
}
this.childPage=new AdminPage();
this.childPage.id=pageData.page+"_"+(Math.floor(Math.random() * (999999999 - 100000000 + 1)) + 100000000);
eval(this.childPage.id+"=this.childPage");
this.childPage.getPage(pageData);
return this;
};
I'm struggling with this even after reading the MSDN documentation and the following online guides:
Codefoster
Stephen Walter
I think my problem is easy to fix and that I just am thinking about something in the wrong way. Basically I am querying my web service and on success running the following method. I am then trying to bind the result to my listview. For now I am using a hardcoded value publicMembers.itemlistwhich has been declared at the top of the document just to make sure I can actually bind to the list before doing it with my query results. Ignore line 2 for now.
Success Method:
_lookUpSuccess: function xhrSucceed(Result) {
var response = JSON.parse(Result.responseText);
listView = document.querySelector("#termTest");
ui.setOptions(listView, {
itemDataSource: publicMembers.itemList,
itemTemplate: document.querySelector(".itemtemplate"),
})
},
Now, instead of using document.querySelector, I have also tried with WinJS.Utilities.id and WinJS.Utilities.query, neither of which worked either. This doesn't break my code and introduce an error but it doesn't bind to the listview either so I think I have an issue in querying the right DOM element. However exactly the same code does work if I add it to the top of the script, it is only when I place it in this method that it stops working.
EDIT: Also to clarify, when I debug publicMembers.itemList is storing the values I expect them to be.
Please point out if I have explained things poorly and I will try and improve my question.
Thanks.
I haven't used WinJS.UI.setOptions, but rather this other way of setting the data source. Can you see if it works?
_lookUpSuccess: function xhrSucceed(result) {
var response = JSON.parse(result.responseText);
listView = document.querySelector("#termTest");
listView.winControl.itemDataSource = publicMembers.itemList;
},
This would assume you're defining the itemTemplate as part of the data-win-options attribute of your ListView's HTML element. You could also probably just do listView.winControl.itemTemplate = document.querySelector(".itemtemplate") if you prefer to set it programmatically.