Breeze - Lazy Load Navigational Property - javascript

New to breeze - I have a breeze entity, that has an array navigational property in a one to many relationship. I'd like to check if the parent entity has any related child entities existing in the navigational property. This child array has not been expanded, and I'd like to lazy load for the check.
With the load happening asynchronously, it seems it may not be loaded by the check
(if entity.children()...).
It would seem I'd have the same issue if I put the check in the "then" callback. Is there a way to synchronously load the child array so that I can do the check and return whether populated or not? Or is there a better way to do this?
function doChildrenExist (entity) {
var childrenExist = false;
entity.entityAspect.loadNavigationProperty("children")
.then(function () {})
.fail(function () {});
if(entity.children() !== null || entity.children().length > 0) {
childrenExist = true;
}
return childrenExist;
}

There is no way to synchronously load a navigation property, ( this is the nature of ajax), but you are on the right track with the loadNavigationProperty. You could do something like the following where you pass in a callback function that will be called with either "true" or "false" depending on whether children exist.
function doChildrenExist(entity, callback) {
entity.entityAspect.loadNavigationProperty("children").then(function () {
// this syntax assumes knockout model library.
var childrenExist = entity.children().length > 0;
callback(childrenExist);
}).fail(function () {
...
});
}

The reason this is not returning what you would like right now is because you are returning the childrenExist before the async query has completed. Putting the function inside your .then() will catch the call back, and only return IT'S callback once the data has been retrieved, unless it fails, in which case we can return an error or simply 'false'.
function doChildrenExist (entity) {
var childrenExist = false;
entity.entityAspect.loadNavigationProperty("children")
.then(function () {
if(entity.children().length > 0) {
childrenExist = true;
}
return childrenExist; })
.fail(catchError);
function catchError(e) {
alert(e.message);
return false;
}
}
to save a few bytes of code you should be able to do this as well -
function doChildrenExist (entity) {
// No need to define a variable since we are either returning true or false
entity.entityAspect.loadNavigationProperty("children")
.then(function () {
if(entity.children().length > 0) {
return true;
}
return false; })
.fail(catchError);
function catchError(e) {
alert(e.message);
return false;
}
}
and in your function that is calling doChildrenExist -
var showChildren = ko.observable(false); // Do something like hide children until they exist
function refresh(entity) {
doChildrenExist(entity)
.then(checkResult)
}
function checkResult(result) {
showChildren(result);
}

Related

Trying to add a property to an object, but it removes all previous instead of adding to it

I have this main function that writes a log, it addds the arg, to an existing object called logContent:
function printToLog(arg) {
LogContent = { ...LogContent, ...arg };
cy.writeFile(filepath, LogContent);
}
This function is used inside of other functions like this one that checks if it is a step or a page:
function printStepToLog(page, step) {
if (step) {
printToLog({
[`${page}`]: {
...LogContent[`${page}`],
[`${step}`]: "passed"
}
});
} else {
printToLog({
[`${page}`]: "passed"
});
}
}
There is a bigger function that checks for warning on the page and if there are it adds them to the log:
function logProgress(page, step) {
cy.wait(800); //give time to let validaiton run
getAndLog(".block-content").within(form => {
let errors = form.find(".mat-error");
if (errors && errors.length > 0) {
[...errors].forEach((error, i) => {
LogContent = {
...LogContent,
[`${page}`]: {
...LogContent[`${page}`],
[`${step}`]: "failed"
},
error: { ...LogContent.error, [`err${i}`]: error.innerText }
};
});
cy.writeFile(filepath, LogContent);
} else {
printStepToLog(page, step);
}
});
}
The problem is that this function getAndLog... doesnt just add to the object, it completely overwrites the obj.. so if it fails, the rest of the infos like the passed pages and steps are gone.
function getAndLog(identifier, altText) {
printToLog({ currentGet: { [altText || identifier]: "not found" } });
cy.get(identifier)
printToLog({ currentGet: { [altText || identifier]: "found" } });
return cy.get(identifier)
}
I am usng the "getAndLog" function instead of cy.get to get elements, as I want to log whats being searched for, and if it was found.
But this makes the log contents be replaced with just the current prop.
Cant see why it overwrites everything instead of just adding the "current" property if, on the "printToLog" which is being used inside the "getAndLog" function, its adding the LogContent content by spreading it?
By default the writeFile function overwrites the file (flag 'w'). Just change the flag as in this example:
cy.writeFile('message.txt', 'Hello World', { flag: 'a+' })
See the documentation here

Vee-Validate validateAll() with scope

I have a scenario where I have sectioned out (scoped) a form so that I can validate small chunks at a time using the following function.
validateScope (scope) {
return this.$validator.validateAll(scope);
}
I want to do one final validation of the entire form before I submit it to the server; however, validateAll() doesn't seem to pick up inputs that have been added to a scope. I've also tried just validating each scope and then submit the form if they are ALL valid, but I am not sure how to do that since everything is asynchronous.
validateAll () {
let valid = true;
// Not sure how to build this function since validateScope is asynchronous
_.each(this.names, (name, index) => {
if (this.validateScope(`name-${index}`)) {
valid = false;
}
});
return valid; // Always returns true even though the _.each should set it to false
}
As mentioned in my comment, your code will end up looking something like this:
validateAll () {
let valid = true;
let validations = []
_.each(this.names, (name, index) => {
validations.push(this.validateScope('name-' + index))
});
return Promise.all(validations)
// consolidate the results into one Boolean
.then(results => results.every(r => r))
}
Then, of course, you'll have to use validateAll as a promise:
this.validateAll().then(isValid => {
if (!isValid) {
//do whatever you need to do when something failed validation
} else {
// do whatever you need to do when everything is valid here
}
})

Pass property with dot notation(sometimes) to function

I've got a function that is looking through a specified collection and highlighting the checkboxes for the items that are present in that collection.
function highlightFiltersPresentInTransactions(collection, collectionproperty, child) {
var found = false;
angular.forEach(collection, function (filterType) {
if (scope.vm.transactions) {
found = scope.vm.transactions.filter(function (obj) {
if (child) {
return obj[collectionproperty][child] === filterType.name;
} else {
return obj[collectionproperty] === filterType.name;
}
});
}
if (found) {
filterType['has-transaction'] = (found.length > 0);
}
});
}
I'm able to call it and it correctly works like this
highlightFiltersPresentInTransactions(scope.filterTypes, 'target', 'type');
highlightFiltersPresentInTransactions(scope.actionTypes, 'transactionType');
What I would like to be able to avoid is the check whether there is a child element that needs to be checked.
I attempted to call the function as such:
highlightFiltersPresentInTransactions(scope.filterTypes, 'target.type');
Since this is a string it doesn't find the property. I also tried creating a blank target object then passing target.type without the quotes.
How can I dynamically pass in a property that might or might not have a child property to my function?
How about passing a function reference to the function?
highlightFiltersPresentInTransactions(scope.filterTypes, function(o) { return o.target.type; });
highlightFiltersPresentInTransactions(scope.filterTypes, function(o) { return o.transactionType; });
This can be implemented pretty easily:
function highlightFiltersPresentInTransactions(collection, readFn) {
var found = false;
angular.forEach(collection, function (filterType) {
if (scope.vm.transactions) {
found = scope.vm.transactions.filter(function (obj) {
return readFn(obj) === filterType.name;
});
}
if (found) {
filterType['has-transaction'] = (found.length > 0);
}
});
}
If you don't want to do that, one way or another you'll have to split your string target.type into separate properties, and do it your existing way (just without the explicit parameter for child).

creating a service in angularJS, using restangular promises

Ok, this is driving me mad, basically what I am trying to do is create a service to get and evaluate user capabilities, I am using WP REST API. I use restangular to get my JSON data.
At this stage I am testing the function in the controller itself, but no matter where I test it, be it in my custom service using this.method or inside the controller, with or without using $scope the result is always undefined. I know I am missing something either in the way I am returning true or false inside the function, or there is something fundamentally different when it comes to promises in JS. Here is the code:
var current_user = parseInt(o2_i18n.user_id),
currentUserCapabilities,
capability;
$scope.currentUserCan = function(capability) {
if(current_user !== '0') {
wpAPIResource.one('users').get()
.then(function(allUsers){
for (var i = 0; i < allUsers.length; i++) {
if ( allUsers[i].id === current_user ) {
var currentUserCapabilities = allUsers[i].capabilities;
for(var prop in currentUserCapabilities){
if (capability === prop) {
//$log.log( prop );
return prop;
} else {
//$log.log( prop );
return false;
}
}
}
}
}, function(reason){
$log.error(reason);
});
} else {
//The user is not logged in, therefor no capabilities
return false;
}
};
$log.log($scope.currentUserCan('publish_posts'));
if ( $scope.currentUserCan('publish_posts') ) {
$log.log( 'Yes I Can!' );
} else {
$log.warn('No Can\'t Do!');
}
Your currentUserCan function doesn't return anything if current_user !== '0'. You should have it return a promise, for example (for the following you'll need to inject the $q service)
$scope.currentUserCan = function(capability) {
if(current_user !== '0') {
// note the "return" here
return wpAPIResource.one('users').get().then(function(allUsers){
for (var i = 0; i < allUsers.length; i++) {
if ( allUsers[i].id === current_user ) {
var currentUserCapabilities = allUsers[i].capabilities;
for(var prop in currentUserCapabilities){
if (capability === prop) {
return prop;
}
}
}
}
return false;
}, function(reason){
$log.error(reason);
return $q.reject(reason); // you still want the promise to fail
});
} else {
return $q.resolve(false);
// this turns the static value into a promise so the API for this
// function is consistent
}
};
then consume the function like this
$scope.currentUserCan('publish_posts').then(function(can) {
if (can) {
$log.log('Yes I Can!');
} else {
$log.warn("No Can't Do!");
}
});
I also cleaned up your loop a little. In your OP, there was no point in the inner loop and you didn't have a return value if the user was not found in the allUsers array.

Replace value with API value in ng-repeat

I am listing a bunch of JSON objects to a view, but only it's category ID is included. Not the name, which I need to display. I have to make a separate $http call to process which items match. Values will not render to view. Code:
$scope.cardCategories = function(id) {
angular.forEach($scope.categoryFilter, function(category, index){
if (category.id == id) {
//console.log(category.name);
return category.name;
} else {
return;
}
})
}
value inside a simplified ng-repeat
<div ng-repeat="topic in latest">
{{cardCategories(topic.category_id)}}
</div>
I have also attempted to write this as a filter, but the value will not display. Console shows the matches. My suspicion is that I have to process the original latest array. Thanks.
.filter('cardCategories', function($rootScope, $state){
return function(catId) {
if (catId !== undefined && $rootScope.cardCategories) {
angular.forEach($rootScope.cardCategories.categories, function(category, index){
if (category.id == catId.category_id) {
return category.name;
} else {
return;
}
})
}
}
})
View:
{{topic.category_id | cardCategories}}
That return statement in the forEach callback function will not return a value from the $scope.cardCategories, it returns something from the callback you provided to forEach (and angular happily ignores it). Try something like this:
$scope.cardCategories = function(id) {
var i = $scope.categoryFilter.length; // assuming it's an array
while(i--) {
if($scope.categoryFilter[i].id === id) {
return $scope.categoryFilter[i].name;
}
}
};
Also -- there's no way to break (stop early) an angular.forEach loop, so for performance reasons it's better to avoid it if you're locating something inside an array/object.
You need to make sure the forEach loop doesn't do a return if the current iteration doesn't match the ID you passed in.
Take out the else condition from your if statement
$scope.cardCategories = function(id) {
angular.forEach($scope.categoryFilter, function(category, index){
if (category.id == id) {
return category.name;
}
});
};

Categories

Resources