KO Single page app. Async data calls randomly failing/not binding - javascript

I've got a single screen with multiple dropdowns. Each get populated via a web api call.
I'm in an active directory environment. I'm using the Durandal SPA framework.
At the moment I do the loading of my dropdowns on the activate of my view. EG.
activate: function (data) {
var id = data.id || undefined;
userRole(data.mode || 'usr');
// i added this as i was thinking i dont need to initialize the "static" dropdowns every time
// I'm testing my code without this as we speak.
if (!hasBeenInit) {
lookupInit(id);
hasBeenInit = true;
}
},
}
[A] It seems like randomly, depending on the amount of server load (I'm guessing) my dropdowns wont get populated..
[B] Some of the dropdowns are dependant on the value of another.
I've I've achieved this by making use of the knockout subscribe event. and updating my observable collections.
Javascript
userInfo.BuildingCode.subscribe(function (value) {
vm.userInfo.FloorCode(undefined);
http.get('api/Floor', { id: value }, false)
.done(function (response) {
console.log('api/Floor');
vm.lookups.allFloors(response);
console.log(vm.lookups.allFloors());
});
});
html
<select data-bind="options: lookups.allFloors, optionsText: 'FloorName', optionsValue: 'FloorCode', value: $root.userInfo.FloorCode"></select>
If I had to select the building value for example, the floor dropdown must get new values.
The API call does happens, it seems like the observable array gets updated, but the values don't seem to be reflecting on the view.
Chrome console log
api/Floor main-built.js:14
[Object, Object, Object]
api/Floor main-built.js:14
[Object, Object, Object]
api/Floor main-built.js:14
[Object, Object, Object, Object, Object]
What can i do to ensure my lookups/dropdowns get populated each and every call. As well as their execution order stays as specified.
I've tried something like this, but it just feels wrong. I've also added an async false overload to the durandal http helper, but it didnt seem to work... any ideas? :\
http.get('api/employmenttype')
.done(function (response) {
console.log('api/employmenttype');
vm.lookups.allEmploymentTypes(response);
})
.then(function () {
http.get('api/actionlist')
.done(function (response) {
console.log('api/actionlist');
vm.lookups.allActionListOptions(response);
})
.then(function () { ... } );
UPDATE
I investigated my async call overload. Ended up being the culprit, once i fixed it, my data seemed to load constantly. The only side effect is that none of my server side data sets loads asynchronously. (Which i'm hoping I wont get crucified for).

Assuming that lookupInit is a async call make sure to return the promise. So activate becomes something along the line.
activate: function (data) {
var id = data.id || undefined;
userRole(data.mode || 'usr');
// i added this as i was thinking i dont need to initialize the "static" dropdowns every time
// I'm testing my code without this as we speak.
if (!hasBeenInit) {
hasBeenInit = true;
return lookupInit(id);
}
return true;
},
I'm still digesting the other part(s) of the question, but thought that might get you started already.
Update: The value of vm.userInfo.FloorCode is reset to undefined, but in the select there's no optionsCaption: 'Please select...' defined. It might be that ko won't update the option values because is can't find a corresponded value.

Related

Using control.setDisabled() after promise

I'm writing some JS for Dynamics 365 which disables (locks) the fields on the selected editable subgrid row.
The method to do this is .setDisabled() (Documentation). I can run the following method which will lock all the fields upon selecting a row:
function onGridRowSelected(context){
context.data.entity.attributes.forEach(function (attr) {
attr.controls.forEach(function (myField) {
myField.setDisabled(foundResponse);
})
});
}
The issue I am having is trying to run the above following a promise. I have the following code which will pass the result of a promise into my disable fields methods:
var gridContext;
function onGridRowSelected(context){
gridContext = context.getFormContext();
//Retrieve the record we want to check the value on
Xrm.WebApi.retrieveMultipleRecords("ms_approvalquery", "?$select=ms_responsetext&$top=1&$orderby=createdon desc")
.then(result => disableOrEnableFields(result));
}
function disableOrEnableFields(result){
//Check if the record found has a ms_responsetext != null
var foundResponse = false
if (result.entities[0].ms_responsetext != null){
foundResponse = true;
}
//Either disable/enable all the row columns depending on the value retrieved from the above
gridContext.data.entity.attributes.forEach(function (attr) {
attr.controls.forEach(function (myField) {
myField.setDisabled(foundResponse);
})
});
}
When stepping through debug, I can see that myField.setDisabled(true); is getting called but nothing is happening. Is this because it's on a separate thread? How do I get back to the main thread with the result of my promise?
Note: Using Async/Await doesn't work either - it gives the same results.
we had similar issues few days back, unfortunately Async/Await/promise call does not respect grid control, you will have to go by old/classic Sync call way and then it shall work. Let me know if this solves your problem.

How to include the icon inside the tooltip message

I need to include the download icon inside the tooltip message, I have tried the below code:
ObjectIconView: Ember.ContainerView.extend(childMOMixin, {
iconDownload: function () {
model: 'download'
},
mouseEnter: function(e) {
if(type == 'Text'){
var textUrl = {content: 'Preview is Not Available, Use '+ this.iconDownload() +'Menu'};
this.$().tooltip(textUrl);
}
}
In that I have called the iconDownload inside the tooltip. But it's saying undefined in the output. I'm using Ember 1.4.0 version. Can anybody please provide the suggestion for this. I'm new to the Ember. Thanks in advance
There are two things that you need to change to get the tooltip to display the data you're expecting.
1: Return something from iconDownload
Right now, the function returns nothing, it makes an internal list and then does nothing.
Does it even need to be a function, or could it just be a string, object?
2: You're not accessing the data in it correctly.
Assuming you're actually needing a hash returned, you're not accessing the data properly - all you're doing is getting the object.
With the structure you have right now, you'r string generator would be
'Preview is Not Available, Use '+ this.iconDownload().model +'Menu'
I'd recommend a couple of additional changes, if they're possible.
1: Use the Ember getter to get data from iconDownload.
Instead of this.iconDownload, call this.get('iconDownload.model') or this.get('iconDownload')
2: Make the actual tooltip text a computed property.
toolTipText: function () {
return `Preview is Not Available, Use ${this.get('iconDownload.model')} Menu`;
}.property('iconDownload.model');
Now, you can just call this.get('toolTipText') to get what the tooltip says.
3: Move your mouseEnter function into the actions section of your view's javascript

Adding an event handler inside a knockoutjs custom binding

I'm a fairly experienced knockout user, so I understand quite a bit of the under the hood stuff, I have however been battling now for a few days trying to figure out how to achieve a given scenario.
I have to create a system that allows observable's within a given knockout component to be able to translate themselves to different languages.
to facilitate this, I've created a custom binding, which is applied to a given element in the following way.
<p data-bind="translatedText: {observable: translatedStringFour, translationToken: 'testUiTransFour'}"></p>
This is in turn attached to a property in my knockout component with a simple standard observable
private translatedStringFour: KnockoutObservable<string> = ko.observable<string>("I'm an untranslated string four....");
(YES, I am using typescript for the project, but TS/JS either I can work with.....)
With my custom binding I can still do 'translatedStringFour("foo")' and it will still update in exactly the same way as the normal text binding.
Where storing the translations in the HTML5 localStorage key/value store, and right at the beginning when our app is launched, there is another component that's responsible, for taking a list of translation ID's and requesting the translated strings from our app, based on the users chosen language.
These strings are then stored in localStorage using the translationToken (seen in the binding) as the key.
This means that when the page loads, and our custom bind fires, we can grab the translationToken off the binding, and interrogate localStorage to ask for the value to replace the untranslated string with, the code for our custom binding follows:
ko.bindingHandlers.translatedText = {
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
},
update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Ask local storage if we have a token by that name
var translatedText = sessionStorage[translationToken];
// Check if our translated text is defined, if it's not then substitute it for a fixed string that will
// be seen in the UI (We should really not change this but this is for dev purposes so we can see whats missing)
if (undefined === translatedText) {
translatedText = "No Translation ID";
}
associatedObservable(translatedText);
ko.utils.setTextContent(element, associatedObservable());
}
}
Now, thus far this works brilliantly, as long as the full cache of translations has been loaded into localStorage, the observables will self translate with the correct strings as needed.
HOWEVER......
Because this translation loader may take more than a few seconds, and the initial page that it's loading on also needs to have some elements translated, the first time the page is loaded it is very possible that the translations the UI is asking for have not yet been loaded into into localStorage, or may be in the process of still loading.
Handling this is not a big deal, I'm performing the load using a promise, so the load takes place, my then clause fires, and I do something like
window.postMessage(...);
or
someElement.dispatchEvent(...);
or even (my favorite)
ko.postbox.publish(...)
The point here is I have no shortage of ways to raise an event/message of some description to notify the page and/or it's components that the translations have finished loading, and you are free to retry requesting them if you so wish.
HERE IN.... Lies my problem.
I need the event/message handler that receives this message to live inside the binding handler, so that the very act of me "binding" using our custom binding, will add the ability for this element to receive this event/message, and be able to retry.
This is not a problem for other pages in the application, because by the time the user has logged in, and all that jazz the translations will have loaded and be safely stored in local storage.
I'm more than happy to use post box (Absolutely awesome job by the way Ryan -- if your reading this.... it's an amazingly useful plugin, and should be built into the core IMHO) but, I intend to wrap this binding in a stand alone class which I'll then just load with requireJs as needed, by those components that need it. I cannot however guarantee that postbox will be loaded before or even at the same instant the binding is loaded.
Every other approach i've tried to get an event listener working in the binding have just gotten ignored, no errors or anything, they just don't fire.
I've tried using the postmessage api, I've tried using a custom event, I've even tried abusing JQuery, and all to no avail.
I've scoured the KO source code, specifically the event binding, and the closest I've come to attaching an event in the init handler is as follows:
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Set up an event handler that will respond to events on session storage, by doing this
// the custom binding will instantly update when a key matching it's translation ID is loaded into the
// local session store
//ko.utils.registerEventHandler(element, 'storage', (event) => {
// console.log("Storage event");
// console.log(event);
//});
ko.utils.registerEventHandler(element, 'customEvent', (event) => {
console.log("HTML5 custom event recieved in the binding handler.");
console.log(event);
});
},
None of this has worked, so folks of the Knockout community.....
How do I add an event handler inside of a custom binding, that I can then trigger from outside that binding, but without depending on anything other than Knockout core and my binding being loaded.
Shawty
Update (About an hour later)
I wanted to add this part, beacuse it's not 100% clear why Regis's answer solves my problem.
Effectively, I was using exactly the same method, BUT (and this is the crucial part) I was targeting the "element" that came in as part of the binding.
This is my mind was the correct approach, as I wanted the event to stick specifically with the element the binding was applied too, as it was said element that I wanted to re-try it's translation once it knew it had the go-ahead.
However, after looking at Regis's code, and comparing it to mine, I noticed he was attaching his event handlers to the "Window" object, and not the "Element".
Following up on this, I too changed my code to use the window object, and everything I'd been attempting started to work.
More's the point, the element specific targeting works too, so I get the actual event, on the actual element, in the actual binding that needs to re-try it's translation.
[EDIT: trying to better answer the question]
I don't really get the whole point of the question, since I don't see how sessionStorage load can be asynchronous.
I supposed therefore sessionStorage is populated from som asynchronous functions like an ajax call to a translation API.
But I don't see what blocks you here, since you already have all the code in your question:
var sessionStorageMock = { // mandatory to mock in code snippets: initially empty
};
var counter = 0;
var attemptTranslation = function() {
setInterval(function() { // let's say it performs some AJAX calls which result is cached in the sessionStorage
var token = "token"; // that should be a collection
sessionStorageMock[token] = "after translation " + (counter++); // we're done, notifying event handlers
window.dispatchEvent(new Event("translation-" + token));
}, 500);
};
ko.bindingHandlers.translated = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = valueAccessor();
var token = val.token;
console.log("init");
window.addEventListener("translation-" + token, function() {
if (token && sessionStorageMock[token]) {
val.observable(sessionStorageMock[token]);
}
});
}
};
var vm = function() {
this.aftertranslation = ko.observable("before translation");
};
ko.applyBindings(new vm());
attemptTranslation();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="translated: { observable: aftertranslation, token: 'token' }, text: aftertranslation" />

how to add partial page functionality using $http service in angular?

I want to perform operation after successfully loaded the partial page using $http service in angular.
The operation is to check the checkbox based on the scope value.
Please anyone help me.
Source Code here:
This is actual code.
$http({
url : './resources/staticPages/custom-object-filter.html',
method : "GET"
}).success(function(data, status) {
$scope.data = data;
jQuery("objectViewPartial").html($compile($scope.data)($scope));
//console.log($scope.selected);
if(angular.equals($scope.selected, "ShowActivatedObjects")) {
$("#ShowActivatedObjects").attr('checked','true');
} else {
$("#ShowActivatedObjects").attr('checked','false');
}
}).error(function(data, status) {
console.log("some error occured partial page");
});
After getting success the below code is not working.
if(angular.equals($scope.selected, "ShowActivatedObjects")) {
$("#ShowActivatedObjects").attr('checked','true');
} else {
$("#ShowActivatedObjects").attr('checked','false');
}
I placed this code inside of success function.
Please advice where i need to place this code.
A short Angularjs excursus:
In general you should minimize the use of jQuery within angularjs as far as you can, because both concepts are working against each other.
Angularjs is based on bindings between scope variables and the template expressions. So for a check box you can easy handle it without jQuery as follows:
A good explanation you can find here:
https://stackoverflow.com/a/14520103/4852206
What you are seeing here is, that you i.ex. can bind the checkbox inputs to an array. If you are changing the array content, the checkboxes are listening and do change there state (checked or unchecked) just on the fly without the need of checking if it is active and without the Jquery dom manipulation needs.
I wrote the above excursus, because you will run into huge unstructured and unreadable code if you will use your way as best practice. Also I am afraid, that you are using your code within your controller function. In Angularjs the controller is NOT for dom manipulation, you should only use it for setting / changing the scope variables or just a minimal business logic (in general avoid it here!) Business logic should go into services and dom manipulation should go into directive's.
Your question:
Please provide more information for your problem / logic. Please provide sample checkboxes, and sample information that you pull from the server via $http
Please provide also what you want to achieve exactly. Then we can help you with a clear answer.
Sorry I know this is no answer, but I cannot add comments actually cause I am quite new here.
EDIT
Ok thanks to your comment to #Likeee I think I got you. You have a set of checkboxes on your page. On page load you want to check which checkbox needs to be active. The logic which checkbox is active on pageload comes from the server side, is that correct? If yes I would handle it as follows with angular structure (I created a fiddle for a better understanding: https://jsfiddle.net/pfk9u3h0/4/)
First you need your HTML:
<body ng-app="myApp" ng-controller="AppController">
<label ng-repeat="filter in productFilter">
<!--
simply checking if the array contains the entry. if not, it is not selected. On a click event, the selected item is added to the array.
-->
<input
type="checkbox"
value="{{filter}}"
ng-checked="selection.indexOf(filter) > -1"
ng-click="toggleSelection(filter)"/> {{filter}}
</label>
"selection Array within SelectionService": <br />{{selection}} <br /><br />
as you see, the array is also updating when you check or uncheck a box
</body>
This HTML snipped does several things:
First it declares which controller (here "AppController" to use.
Second it creates a label with a checkbox inside. The label has the Angularjs directive ngRepeat and causes to show as much checkboxes as the array "productFilter" within AppController contains.
The Javascript contains a controller which is talking to your HTML and to a service. The service is also declared below.
Controller:
app.controller("AppController", function( $scope, SelectionService ) {
// available filter checkboxes
$scope.productFilter = ['Red pants', 'Green Pants', 'Yellow pants', 'Blue pants'];
// selected filter
// normally you just would have this: $scope.selection = ['Red pants', 'Blue pants'];
// But now you want to pull the selection elsewhere:
// The selection should go in a service, because it can happen, that other pageviews of your page shall
// change the values or even get the selection as well.
$scope.selection = SelectionService.selection; // Bind the selection to the service
// With the proper binding, you can change the selection within other modules / pages etc. and this
// checkbox fields will update based on the service data!
//I just initialize the service selection array, butbut you really want to pull it from server like this:
// SelectionService.loadSelection()
// .then(function(data) {
// if you like, yo can do sth. with this promise callback
// });
// Change selection for the clicked input field
$scope.toggleSelection = function toggleSelection(filter) {
var idx = $scope.selection.indexOf(filter);
// is currently selected
if (idx > -1) {
SelectionService.removeSelection(idx);
}
// is newly selected
else {
SelectionService.addSelection(filter);
}
};
});
With $scope.productFilter = ['Red pants', 'Green Pants', 'Yellow pants', 'Blue pants']; we declare an array over which we iterate within our HTML. This contains all checkboxes with names.
Now we need an array with all selected checkboxes. This is saved in $scope.selection and is bound to the service which i show at the end:
$scope.selection = SelectionService.selection;
The rest within the controller is just to set the new values, if you uncheck or check a checkbox
The Service:
app.service("SelectionService", function ($http, $q) {
var obj = {
selection : ['Red pants', 'Blue pants'],
loadSelection : function(){
var that = this;
var deferred = $q.defer();
var config = {
responseType : "json",
cache: true
}
$http.get("/getSelectionFromServer", null, config)
.success(function(response){
if(response) {
// IMPORTANT: Use a deep copy, otherwise you will loose the binding
angular.copy(response,that.selection);
}
deferred.resolve(response);
})
.error(function(){
deferred.reject();
});
return deferred.promise;
},
addSelection : function (filter) {
this.selection.push(filter)
},
removeSelection : function (index) {
this.selection.splice(index, 1);
};
return obj;
});
The Service is doing 4 things: It holds and initilizes (if you like) an array "selection". And it offers a method to load new data from server and save it to the array. Here it is important that you use the copy method, otherwise you will loose any binding. And it offers methods to set and remove selections.
in my example i dont use the load method, because I do not have any serverside scripts here...I just take the initialization values. But you should use my load method.
Hi your comparison is probably bad.
if(angular.equals($scope.selected, "ShowActivatedObjects")) {
I don't know what value is in $scope.selected, but i think this value is a checkbox state which is boolean type. So you are comparing boolean with string.
Check out https://docs.angularjs.org/api/ng/function/angular.equals
Finally i achieved my requirement:
// $scope.getCustomObjectsBasedOnObjectTypeSelection = getCustomObjectsBasedOnObjectTypeSelection;
$scope.selected = ['Show All'];
var IsActive = "";
$scope.objectSelection = function objectSelection(objectType) {
var idx = $scope.selected.indexOf(objectType);
// is currently selected
if (idx > -1) {
$scope.selected.splice(idx, 1);
}
// is newly selected
else {
$scope.selected.push(objectType);
}
};

jQuery/javascript basic logic question

I am using a jQuery method $.getJSON to update data in some cascading drop down lists, in particular, a default value if there is nothing returned for the drop down, e.g. "NONE".
I just want some clarification to how my logic should go.
var hasItems = false;
$.getJSON('ajax/test.json', function(data) {
hasItems = true;
//Remove all items
//Fill drop down with data from JSON
});
if (!hasItems)
{
//Remove all items
//Fill drop down with default value
}
But I don't think this is right. So do I enter into the function whether or not I receive data? I guess I really want to check the data object contains something - to set my boolean hasItems.
You should handle the check right inside the callback function, check the example here.
var hasItems = false;
$.getJSON('ajax/test.json', function(data) {
hasItems = true;
//Remove all items
//Fill drop down with data from JSON
if (!hasItems)
{
//Remove all items
//Fill drop down with default value
}
});
You want to do all checking of returned data inside the callback, otherwise that condition will be called before the callback has been called, resulting in it always being the initial value assigned.
You're dealing with asynchrony, so you need to think of the code you're writing as a timeline:
+ Some code
+ Fire getJSON call
|
| server working
|
+ getJSON call returns and function runs
The code inside the function happens later than the code outside it.
Generally:
// Setup any data you need before the call
$.getJSON(..., function(r) { //or $.ajax() etc
// Handle the response from the server
});
// Code here happens before the getJSON call returns - technically you could also
// put your setup code here, although it would be weird, and probably upset other
// coders.

Categories

Resources