Custom and first options in ember-power-select - javascript

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', []);

Related

Filtering smart-table on transformed data

Apologies in advance, I am not a very experienced JS programmer, and even less so with AngularJS, but am trying to make some improvements on a legacy codebase that is using Angular 1.5.9 and Smart Table to display information from a database.
I've read all about st-search and st-safe-src vs. st-table, etc., but I am having trouble filtering on my table, since there are transformations happening on the underlying data before it gets displayed. The ng-repeat variable in my case is transaction, which has various fields to hold information for that transaction, such as payee, which holds a UUID pointing to another database document. In the app, we display the name of that payee using a function from another controller (dbCtrl.getPayeeName()), but the underlying data is the UUID. Thus, when attempting to filter with Smart Table, it does not filter on the displayed names, and only works when entering the UUID into the filter field.
A small example (with lots of the intervening bits removed, but hopefully enough to demonstrate my confusion):
<div class="account"
st-table="displayedTransactions"
st-safe-src="transactions"
disable-ng-animate>
...
<div><input st-search="payee" placeholder="search for payee" class="input-sm form-control" type="search"/></div>
...
<div ng-repeat="transaction in displayedTransactions track by transaction.id">
...
<div class="account__td" transaction-field-focus-name="payee">
{{dbCtrl.getPayeeName(transaction.payee)}}
</div>
...
</div>
Is there a relatively simple way to get the filtering to work for a situation like this where the displayed data is different than the underlying data? From what I'm reading in the documentation, it sounds like this might require some sort of custom plugin, which sounds like more work, but I could maybe figure out. I just wanted to see if I'm missing something obvious before heading down that route.
Circling back on this, I was able to accomplish what I needed using the st-set-filter attribute as described in the Strict mode filtering section of the documentation, as well as this helpful answer from laurent back in 2014.
Essentially, I added st-set-filter="transactionFilters" to my table in my html template, as well as input tags with st-search="prop_to_search" attributes. Then in my applications module (I put this in one of the controllers, not sure if that's totally correct) I defined a filter such as below. expression gets passed into this code as an object with string values for whatever you typed in, so if you had three search fields, you'd get an object like:
{
"prop_to_search1": "value1",
"prop_to_search2": "value2",
"prop_to_search3": "value3"
}
In the filter function, I wrote an if block for each property that could come in, and then do my custom transformations and pattern matching there. This way, I have full control over the eventual matching, and rather than searching on the UUID, I can do something like $rootScope.dbCtrl.getPayeeName(uuidToSearch) instead. This is all acceptably performant in my use case, but I could probably cache those database lookups as a potential optimization.
angular.module('myApp').filter('transactionFilters', function($rootScope, $filter){
return function(array, expression){
// console.log(`expression is: ${JSON.stringify(expression, null, 4)}`)
return array.filter(function(val, index){
// in this function's context, `expression` is an object with
// the active filters entered in each field; `val` is the data
// representation of each row of the table
// if this function returns true, the row will match and smart-table
// will show it, otherwise it will be hidden
// define matches all at once, check them all, then return
// a big logical AND on all of them
let accountMatch = true;
let payeeMatch = true;
let categoryMatch = true;
if (expression.account) {
uuidToSearch = val.account // this is the account UUID
strToSearch = $rootScope.dbCtrl.getAccountName(uuidToSearch).toLowerCase(); // convert to an account name (we could memoize this to improve performance)
if (strToSearch) {
// if the account had a name (it always should, but catch in case)
// then check if the row's account contains the text entered in the filter field
accountMatch = strToSearch.includes(expression.account.toLowerCase());
} else {
accountMatch = false;
}
}
if (expression.payee){
if (val.payee) {
uuidToSearch = val.payee
strToSearch = $rootScope.dbCtrl.getPayeeName(uuidToSearch).toLowerCase();
}
if (strToSearch) {
payeeMatch = strToSearch.includes(expression.payee.toLowerCase());
} else {
payeeMatch = false;
}
}
if (expression.category) {
if (val.category) {
strToSearch = $rootScope.dbCtrl.getCategoryName(val.category, val.date).toLowerCase()
categoryMatch = strToSearch.includes(expression.category.toLowerCase())
} else {
categoryMatch = false;
}
}
return (
accountMatch &&
payeeMatch &&
categoryMatch
)
})
}
});

Autocomplete in Google Apps Script with data from Google Sheets

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);
}

Adding a selectable 'not found' entry to a Twitter Typeahead Control

I've been combining Twitter Typeahead and Bloodhound into a Knockout custom binding as a bit of an experiment, and so far I have it working quite well. However I have a use case where I wanted to have a selectable entry in the list if a user types in a search term which finds no results. This isn't something that is available by default in typeahead but I did find this issue on Github which demonstrates a workaround that seems to fit the bill. Trouble is I can't get it to work at all and my fairly limited Javascript smarts have all but run out.
The gist of this workaround is that rather than creating a Bloodhound instance and then setting the source property of the typeahead to engine.ttAdaptor() you do the following:
var engine = new Bloodhound(/* ... */);
engine.initialize();
var emptyDatum = { val: 'i am suggestion shown when there are none!' };
var sourceWithEmptySelectable = function(q, cb) {
engine.get(q, injectEmptySelectable);
function injectEmptySelectable(suggestions) {
if (suggestions.length === 0) {
cb([emptyDatum]);
}
else {
cb(suggestions);
}
}
};
$('.typeahead').typeahead(null, {
// ...
source: sourceWithEmptySelectable
});
Using the latest versions of typeahead (v0.11.1) I get an error which simply says missing source. Looking at the source code for Bloodhound it looks to me like the engine.get(q, injectEmptySelectable) call no longer works as there is no method of get on the Bloodhound class with a signature that accepts a query and a callback. There is one on the Transport class but I'm not seeing how that would be the one being used in this example. Am I correct in this or am I missing something, or is there another way to accomplish this?

link 2 javascript objects

I am working on an angularJS app and i found a problem that I cant solve. I have a variable with predefined text that I want to replace in an email with the actual values, it looks like this:
$scope.predefinedVars = {
'client_name': $scope.itemPartner.name,
'client_city': $scope.itemPartner.city,
'client_county': $scope.itemPartner.county,
'client_address': $scope.itemPartner.address,
'client_phone': $scope.itemPartner.phone,
'client_email': $scope.itemPartner.email
};
and so on...
Now later on, when I choose a partner, the itemPartner object changes the data. For example: i have a function to watch when I change the partner from a select box:
$scope.$watch('newContract.partner_id', function() {
$scope.itemPartner = _.where($scope.listPartners, {'id': $scope.newContract.partner_id})[0];
alert(JSON.stringify($scope.itemPartner));
});
Now, in the alert, I can see the itemPartner data has changed, but if I try to read the values from my 1st variable $scope.predefinedVars, the values are still empty and do not change.
Is there a way to force the values to change when I change the itemPartner object ?
The problem is that you have set the fields of your predefinedVars object to a primitive. In javascript, primitives are passed by value, not by reference (google if you're not sure what that means).
The result is that any reference to the object you used to set it originally is lost.
You have a couple of alternatives:
Instead of replacing the email using the data from $scope.predefinedVars, use the data from $scope.itemPartner. This is less work.
create a function that repopulates predefinedVars
e.g.
function populatePredefinedVars (partner){
$scope.predefinedVars = {
'client_name': partner.name,
'client_city': partner.city,
'client_county': partner.county,
'client_address': partner.address,
'client_phone': partner.phone,
'client_email': partner.email
};
}
$scope.$watch('newContract.partner_id', function() {
$scope.itemPartner = _.where($scope.listPartners, {'id': $scope.newContract.partner_id})[0];
populatePredefinedVars($scope.itemPartner);
});

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.

Categories

Resources