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);
});
Related
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
)
})
}
});
Okay lets say i enter these two values in my HTML page: 20, 30.
I have these two variabels, i get the value from my HTML page, and it works fine, so BrugerFugt=20, and BrugerTemp=30:
BrugerFugt = document.getElementById("40").value;
BrugerTemp = document.getElementById("41").value;
Then i take these two variabels and use setItem.
sessionStorage.setItem("BF", JSON.stringify(BrugerFugt));
sessionStorage.setItem("BT", JSON.stringify(BrugerTemp));
At this point if i console.log(JSON.stringify(BrugerFugt)), it's printing 20, as it should.
Now i want to send this to my server, so i call this variable:
serverConnection.send(JSON.stringify(SetBruger));
I have defined this variable, that i'm sending to my server.
var SetBruger = {
command: "BrugerIndtastTF",
brugerT: sessionStorage.getItem("BT"),
brugerF: sessionStorage.getItem("BF")
}
Then i tryed to print my JSON.stringify(SetBruger), but it's printing the last value i entered on my HTML web, not the new. Let's say before i entered 20 and 30, i entered somthing like 40 and 60. Then when i print my SetBruger after i've entered 20 and 30, i get this print in my termiinal.
test: {"command":"BrugerIndtastTF","brugerT":"\"40\"","brugerF":"\"60\""}
It's like the problem occurs, when i JSON.stringify(SetBruger)?
Your object-properties are set when the object is created.
var SetBruger = {
command: "BrugerIndtastTF",
brugerT: sessionStorage.getItem("BT"), // set when object is created
brugerF: sessionStorage.getItem("BF") // set when object is created
}
You can wrap that object with an helper-function that updates the object prior return or replace it with a function that returns an freshly created object with the values from sessionStorage like this:
function SetBruger() {
return {
command: "BrugerIndtastTF",
brugerT: sessionStorage.getItem("BT"),
brugerF: sessionStorage.getItem("BF")
}
}
serverConnection.send(JSON.stringify(SetBruger));
To be able to re-use that object and update other properties of it, a helper function that updates the two properties with the content of localstorage would be a good solution.
var SetBruger = {
command: "BrugerIndtastTF",
brugerT: sessionStorage.getItem("BT"), // set when object is created
brugerF: sessionStorage.getItem("BF") // set when object is created
}
// Other code that maybe modify the object
function updateBrugerObject() {
SetBruger.brugerT = sessionStorage.getItem("BT");
SetBruger.brugerF = sessionStorage.getItem("BF");
}
updateBrugerObject();
serverConnection.send(JSON.stringify(SetBruger));
what the best solution for you is, depends on how and where you plan to use the object.
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'm trying to figure out how to programmatically insert a variable helper into a template using the Blaze API. I believe it needs to be done using some form of:
Blaze.render(Blaze.with('variable', contentFunction);
However, I'm struggling to get the contentFunction to work properly. I tried a function that returns a Session variable:
var session_var = function() {
return Session.get('myVariable');
};
However, I keep getting undefined errors. I know this could be done if I defined a separate template to be rendered, but that seems a bit much for this particular case. I was wondering if someone could explain how Blaze.with and Blaze.view work. Also, is it possible to programmatically insert just a variable helper into a template?
Without knowing exactly what you're trying to do, it's not clear if this is the easiest way of going about things.
However, if you look at the docs, you'll see that you can either supply the session variable in a data context and use Blaze.With, or supply no data context and use Blaze.View instead (which appears to be what you're going for).
So, if you want to render a reactive data context, it has to be supplied in the first argument to Blaze.With. Try something like this:
var myView = Blaze.With(
function() {
return {
foo: Session.get('foo')
};
},
function() {
return Spacebars.mustache([
'the value of foo is ',
this.lookup('foo')
]);
}
);
Blaze.render(myView, document.body); // or whatever element you want to render it inside
Alternatively, for Blaze.View:
var myView = Blaze.View(
function() {
return 'the value of foo is ' + Session.get('foo');
}
);
Blaze.render(myView, document.body);
To be honest, the easiest way to learn about the structure of renderable content (which is the difficult bit if you're doing this programmatically) is to study the stuff that the Spacebars compiler churns out. In other words, find an interesting template/sub-template that's been generated from quasi-HTML, pick an element out of it and run Blaze.getView([ELEMENT])._render, then study the result.
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.