Calculated value not being updated correctly using donejs - javascript

I have standard donejs project with a component and a model both created using the default generators like this:
donejs add component storyBoard story-board
donejs add supermodel story
I left a sample project here: https://github.com/riescorp/donejs-promise-iterate
If you run this example using donejs develop and go to http://localhost:8080 you'll see something like this:
The problem is that whenever you change a value of the number of pages (input boxes) for any story, the sum won't update its value. I tried almost everything, from promises to already-resolved-promises but nothing seems to do the trick.
Somehow it seems that when any of those values change, there is no update trigger at all. I also tried setting the results as a List (can.List) but with no luck.

The problem seems to be that elem.pages should be elem.attr("pages").
Here's a version of the code that works for me:
export const ViewModel = Map.extend({
define: {
storyPromise: {
get: function() {
return Story.getList({});
}
},
stories: {
get: function(last, resolve) {
this.attr("storyPromise").then(resolve);
}
},
sum2: {
get: function() {
var total = 0;
if (this.attr('stories') !== undefined) {
this.attr('stories').each(function(elem) {
total += parseInt( elem.attr("pages") );
});
}
return total;
}
}
}
});
You have to use .attr, otherwise, sum2 doesn't know when the pages property changes.

Related

Knockout pureComputed Not Working

I'm having a ridiculous time trying to handle addresses using Knockout. My structure is something like:
viewModel.buildings()[0].Address()...
Where Address is:
var Address = function () {
var self = this;
self.cAddr1 = ko.observable("");
self.cCity = ko.observable("");
...
self.cDisplay = ko.pureComputed(function () {
return self.cAddr1() + '<br>' + self.cCity() + ...;
}
self.AddressActions = new AddressActions();
}
Everything seems to work ok. Each building has an address and the observables are updated properly. The cDisplay also works correctly.
I am wanting to add another computed/observable/whatever that will call a function that is part of AddressActions when the address changes. I tried this, but the console.log never even gets hit which doesn't make any sense to me:
var Address = function () {
// Same as above...
...
self.triggerAddressVerify = ko.pureComputed(function () {
console.log('here');
self.cAddr1(); self.cAddr2(); self.cCity(); self.cState(); self.cZip();
self.AddressActions.VerifyAddress(self);
}
}
Any ideas why this isn't working?
Result
So I'm still new to knockout.js (obviously) but it works a little bit differently than I thought. I basically used the accepted answer but wrapped everything into a pureComputed. Here's what I ended up adding:
var Address = function () {
...
self.addressChangeEvent = ko.pureComputed(function () {
return self.cAddr1() + self.cAddr2() + self.cCity() self.cState() + self.cZip();
}
self.addressChangeEvent.subscribe(function () {
self.AddressActions.VerifyAddress(self);
}
}
it looks like you're looking for subscribe rather than computed
self.cAddr1.subscribe(function(){
self.AddressActions.VerifyAddress(self);
});
you can add a subscription for each variable you need an event for
The Knockout documentation for pure computed specifically says to not use it when you intend to perform some action (side effects).
You should not use the pure feature for a computed observable that is meant to perform an action when its dependencies change.
You can use a regular computed instead.
self.triggerAddressVerify = ko.computed(function () {
console.log('here');
self.cAddr1(); self.cAddr2(); self.cCity(); self.cState(); self.cZip();
self.AddressActions.VerifyAddress(self);
});
But note that this will run once initially as well as on future changes. If you only want to perform an action on future changes, your approach of subscribing to a pure computed is better.

Angular2: Directive variable change, update element

I'm currently trying to give classes to an wrapper that contains all of my app, i usually find this handy for giving certain states like when the header is fixed, or the menu's are opened etc.
So upon reading through some the docs of angular i should probably use an 'Directive'. Now i got this all set up and it looks like this:
constructor(private router:Router, #Inject(DOCUMENT) private document:Document, el:ElementRef, renderer:Renderer) {
this.setClasses(el, renderer);
}
setClasses(el:ElementRef, renderer:Renderer) {
renderer.setElementClass(el.nativeElement, 'header-fixed', this.headerFixed);
}
#HostListener("window:scroll", [])onWindowScroll() {
let number = this.document.body.scrollTop;
if (number > 100) {
this.headerFixed = true;
} else if (this.headerFixed && number < 10) {
this.headerFixed = false;
}
}
Now this is working perfectly but as you can see i'm toggling the headerFixed variable depending on scroll position. However i could of course run the function setClasses() again and it will work but is there anyway to subscribe/watch the variable and update automatically when changed?
Or is there even a better way of achieving wat i'm trying to do?
You can use #HostBinding like:
#HostBinding('class.header-fixed') get myClass() {
return someCondition;
}
Plunker Example

Knockout JS and iPad update issue on text area / wysiwyg

i have an application built with knockout that utilises a wysiwyg called redactor (not that I think that is relevant to this problem but just in case)
I have just run into an issue where a user has an iPad and when they save a 'note' it is missing the last letter each time - nobody else has ever had this issue.
It is evident that this is down to the underlying text area not updating on the last key press, but I am not sure how to fix this.
I have a custom binding as follows
ko.bindingHandlers.redactor = {
init: function(element, valueAccessor) {
var value = valueAccessor();
if (ko.isObservable(value)) {
$(element).redactor({
changeCallback: value,
fileUpload: 'url',
fileManagerJson: site_URL + 'files/files.json',
plugins: ['filemanager', 'clips', 'textexpander', 'bufferbuttons'],
textexpander: [
["##s", "<strong>(S)</strong> - "]
]
});
}
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || '';
if (value !== $(element).redactor('core.getTextarea').val()) {
$(element).redactor('code.set', value );
}
}
}
How can i change this to make sure it behaves as expected on the iPad and updates on the final key press? Or is there a way of simply forcing the update on keyPress / keyDown within an update.
The code samples in the documentation retrieve the widget value like this:
$('#redactor').redactor({
callbacks: {
change: function()
{
console.log(this.code.get());
}
}
});
In other words, they call this.code.get() in the callback body.
Your setup on the other hand...
$(element).redactor({
changeCallback: value
});
it implicitly uses the first argument to the callback as the value.
Does it make a difference if you use the same approach the documentation suggests?
$(element).redactor({
changeCallback: function () {
value(this.code.get());
});
});

Meteor - Where to put calculating methods properly

I'm reading and watching Tutorials about Meteor since 1-2 Weeks. I've learned about how to structure a meteor app regarding server and client side code, accounts, security etc.
What i could not figure out:
Where do i put the calculation logic properly?
For example:
A user puts data in a form and the data is saved in the database. Depending on this input data i want to do several calculations by putting the data through lets say a chaining of around 20 Methods, and finally display some results.
At the moment i have all of these Methods inside the file where the Template.displayResults.helper is.
When i put them in another file they don't get recognized, i think because of the wrapper Meteor puts around.
Example: I have a collection of DIY projects and each of the projects has a field with an array of utilities that are neccessary for the project.
Projects = new Mongo.Collection('projects');
/*
exampleProject = {
"name": "Kitchen table",
"utilities": ["Hammer", "Glue"]
}
*/
I want to display all possible DIY projects depending on the utilities the user has checked.
The UI has a group of checkboxes via the user can select a bunch of utilities he wants to use.
These values are saved in a collection.
Utilities = new Mongo.Collection('utilities');
/*
exampleUtility = {
"name": "Hammer",
"checked": true
}
*/
Then i want to calculate the possible Projects...
Template.displayResults.helpers({
projectsPossible: function () {
var utilitiesCheckedDB = Utilities.find({
checked: true
}).fetch();
var projectsAll = Projects.find().fetch();
return projectsPossible(utilitiesCheckedDB, projectsAll);
}
});
// Returns an array of all possible projects depending on the selected utilities
function projectsPossible(utilitiesCheckedDB, projectsAll) {
var result = [];
_.each(projectsAll, function (project) {
if (project.utilities.length === _.intersection(project.utilities, checkedCheckboxesList(utilitiesCheckedDB)).length) {
result.push(project);
}
});
return result;
}
// Returns an array of all checked utilities in the current checkbox database
function checkedCheckboxesList(checkedCheckboxesDB) {
var result = [];
_.each(checkedCheckboxesDB, function (checkbox) {
result.push(checkbox.name);
});
return result;
}
The question is: There are more methods like "projectsPossible" and "checkedCheckboxesList". Where do i put these methods to get a good structure?
Thanks in advance!
Vin
If you want to register global helpers, just use Template.registerHelper(name, function), for instance:
Template.registerHelper('projectsPossible', function() {
var utilitiesCheckedDB = Utilities.find({
checked: true
}).fetch();
var projectsAll = Projects.find().fetch();
return projectsPossible(utilitiesCheckedDB, projectsAll);
});
If you want to make the functions projectsPossible(utilitiesCheckedDB, projectsAll) or checkedCheckboxesList(checkedCheckboxesDB) accessible from other (client) files, you can make them global. For example:
projectsPossible = function(utilitiesCheckedDB, projectsAll) {
var result = [];
_.each(projectsAll, function(project) {
if (project.utilities.length === _.intersection(project.utilities, checkedCheckboxesList(utilitiesCheckedDB)).length) {
result.push(project);
}
});
return result;
};
You can make model classes, using the transform option for collections. For an ES5 example, see the docs: http://docs.meteor.com/#/full/mongo_collection
Also, you have to make that model class or function global by not using var.
(function() {
foo = function foo() {
alert("fooh")
}
})()
In the above example, without foo =, the foo function would only be visible inside its own file because of the wrapper.

Angular JS $scope, databinding, and variables changing.

So I have an APP I'm working on in Angular JS v1.4.0 and I'm running into a scoping issue. There's a section that has a form that needs to be submitted, but the data needs to be modified before its sent over. I'm currently trying to do this in the javascript before making the call to the server.
I have $scope.msgEditor, that is an object of a bunch of different values that are necessary for the form, as well as the message variables itself. The important part looks something like this:
msgEditor [
msg: {
groups: {
selected: {
0: '1',
1: '2',
}
}
}
]
I'm trying to take this $scope variable, assign it to a local variable, and begin parsing the data like such:
$scope.formOnSubmit = function () {
formattedMessage = formatDataForSave($scope.msgEditor.msg);
};
function formatDataForSave(message) {
message.groups = message.groups.selected.join(', ');
return message;
}
What I want to happen, is $scope.msgEditor.msg to not change at all, and formattedMessage to be returned from the second function, so it can be placed into a $http call. However, the join changes message, formattedMessage, AND $scope.msgEditor.msg
I did a bit more testing, to see what was happening:
$scope.formOnSubmit = function () {
$scope.test = $scope.msgEditor.msg;
var formattedMessage = $scope.test;
formattedMessage = formatDataForSave(formattedMessage);
};
And found that the change made to formattedMessage, would change $scope.test, which would change $scope.msgEdtior.msg.
Any direction on why this is happening, or how to prevent it would be amazing.
I believe you are confusing about passing arguments into functions in javascript: in javascript all arguments are passed by reference so the consequence is what you are experiencing. Have a look at angular.copy function.
https://code.angularjs.org/1.3.17/docs/api/ng/function/angular.copy
I cannot test this but you could try:
$scope.formOnSubmit = function () {
var msgCopy = angular.copy($scope.msgEditor.msg);
formattedMessage = formatDataForSave(msgCopy);
};
function formatDataForSave(message) {
message.groups = message.groups.selected.join(', ');
return message;
}

Categories

Resources