On my SharePoint there is a website where a single list-item is loaded based on a user's selection with JavaScript and CSOM. This list item has a total of ~60 properties defined in it's list definition.
In HTML input fields the user can modify most of the properties after jQuery filled in the loaded properties to their corresponding input fields. When the "save" button is pressed, the properties are collected from the inputs via jQuery and put into a simple JS-object (itemProps):
var itemprops = {
'foo': $('#foo-input').val(),
'bar': $('#bar-input').val()
}
Then, the following function gets called:
function updateListItem(itemProps, onItemAdded, onItemError) {
var list = web.get_lists().getByTitle('ListTitle');
var listItem = list.getItemById(id);
for (var propName in itemProps) {
if (itemProps.hasOwnProperty(propName)) {
listItem.set_item(propName, itemProps[propName]);
}
}
listItem.update();
context.executeQueryAsync(
function() {
onItemAdded(listItem);
},
onItemError
);
}
Debugging shows me, that the data in itemProps are valid. But sometimes (I can't reproduce that effect deterministically) some properties get lost and when I look at the list item in the list on the SharePoint some of the properties are empty, as if itemProps had null or "" associated to that property. When I first tried to debug this I simply created an item and saved it (correctly, with all properties) and then loaded and saved it again without modification but some properties got lost.
Other properties get updated correctly and sometimes this doesn't happen at all.
Is there any way to make sure this effect does't occur or at least to detect it and retry updating the data, before the user's inputs get lost?
I notice you're not invoking context.load(listItem) before calling context.executeQueryAsync() which might cause issues with the listItem object's values being stale or dehydrated.
The code in your question looks like it should still be setting the specified values correctly on the list item, although there may be some code later on (such as in the onItemAdded function) that's running into false assumptions or subtle data differences from the inadequately loaded list item.
Related
I'm running into an issue with KnockoutJS where it appears the data-bindings aren't updating as expected.
In my view model, I have an array of objects with an Enabled property that I initialize to True, and have that property bound to enable on a checkbox with a click handler CheckItems:
self.Answers(data.filter(function (step) {
if (step.Type == 0) {
step.Enabled = true;
return true;
}
else {
return false;
}
}));
...
<ul data-bind="foreach: $root.Answers">
<input type="checkbox" data-bind="click: $root.CheckItems, ..., enable: Enabled>
</ul>
Everything work fine and dandy, until the CheckItems handler runs and I try to set the Enabled property to false:
self.CheckItems = function (step) {
...
self.Answers().forEach(function (option) {
<call my handler>.done(function (data) {
option.Enabled = data;
}
);
});
// EDIT: added valueHasMutated() call here
self.Answers.valueHasMutated();
return true;
}
When I hit return true, I can inspect the self.Answers() object and it shows that the answers that should be disabled have Enabled = false (correctly), but once we're the handler, none of the checkboxes are disabled, and clicking on any other checkbox present and going through the handler shows that the Enabled property seems to have been reset to True. I triple checked and made sure there's nothing else touching the Enabled property anywhere in the code between checkbox clicks.
The binding itself seems to be working, too, since when I switch the initial Enable set to false, all of the checkboxes are disabled.
I'm making changes to the self.Answers array various other places in the script as well, and those go through fine, when a change is made it goes into knockout-latest.debug.js and lands in the notifySubscribers function with the appropriate updates to make. Maybe there's something special about the click event for input fields in knockout?
Any ideas? Still fairly new to Knockout overall, but I thought this was the entire point, that I could update a property in the Answers() observable array and it would update in the corresponding UI.
Edit: based on comments I tried calling valueHasMutated() after the edits were made, but it still isn't updating properly.
Edit2: tried making a copy of the self.Answers array, doing the handler calls to update Enabled, then setting with self.Answers(newAnswers), and this had the same result.
I was advised that the issue was occurring because properties of the objects in CurrentAnswers() weren't observable as well. Since there weren't changes to the array itself, knockout wasn't registering that it had to rebind anything, which is why calling valueHasMutated() didn't change anything as the array looked exactly the same before and after CheckItems() from an array standpoint (also why updating with a copy of the array didn't work, same issue). When setting CurrentAnswers() to a blank array (self.CurrentAnswers([])), then setting it to the updated value, things changed properly since KO recognized the actual array changes.
The solution I ended up using was to utilize ko.mapping.fromJS() method to fill CurrentAnswers() with objects that had observable properties. After some syntax changes in the HTML and JS to properly reference the new observables, everything worked properly.
This is my first question in Stack Overflow. I'll try to be specific but I don't know how to keep this short, so this is going to be a long post. Sorry about that. I promise I searched and tried a lot of stuff before asking, but I'm kind of lost now.
I'm developing a simple app in Angular 6 to keep track of software requisites and the tests associated to those requisites.
I have a component, called RequisiteList, whose HTML part consists in a mat-table with an Array of my own Requisite model class as [dataSource]. This array is received as an #Input parameter, and it also has an #Output parameter which is an EventEmitter that notifies and passes to the parent component every time a Requisite on the list is clicked.
I make use of RequisiteList inside of ReqListMain, which is a component consisting on the list and a hierarchical tree for filtering. This component is working fine, showing, and filtering requisites as intended. This component also captures the #Output event of the list and passes it as an #Output to its parent.
Finally (for what it's related to this question), I have a TestView component that has both an instance of RequisiteList to show the requisites currently associated to current test, and an instance of ReqListMain to add new requisites to current test (like a "browser"). This TestView has an instance of the model class Pectest corresponding to the test that is being currently visualized, which has an array of Requisite.
The idea in this last component was that whenever a requisite of the "browser" list was clicked, it was added to the current test's list. In order to do that, in the callback method associated to the #Output event of the browser list, I tried to add the Requisite received as a parameter:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
console.log('Current test: ');
console.log(this.currentTest);
}
In the HTML part of TestView, I inserted the RequisiteList component like this:
<app-requisitelist [requisites]="currentTest.requisites" ngModel name="reqlistview"></app-requisitelist>
(The ngModel property is part of the things I've been trying, I'm not sure it's necessary).
The result is:
The clicked requisite is not shown in the list.
In the console output I can see the content of currentTest object, and I verify that clicked requisites are in fact added to the requisites array of that object, so the event fires and the object is passed upwards by the children components.
I'm not sure if my problem is that data binding is made by value (I don't think so, as I bind an Array, which is an object AFAIK), or the table is not detecting data changes (I've tried to force data change detection with ChangeDetector), or anything else.
You pass a array to the app-requisitelist component. This component waits this array changes to update the content. When you do this.currentTest.requisites.push(requisite), the array this.currentTest.requisites doesn't change, I mean, if you do
const tmp = this.currentTest.requisites;
this.currentTest.requisites.push(requisite)
if (tmp === this.currentTest.requisites) {
console.log('The arrays are the same');
}
You will get the log printed. So, I suggest do something like that:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
this.currentTest.requisites = this.currentTest.requisites.map(item => item);
console.log('Current test: ');
console.log(this.currentTest);
}
The inserted line forces this.currentTest.requisites to be a new array with the same content.
I'm making a web app with Angular however I'm having trouble with the load button. So the user presses the load button, selects a save to load, and a new set of properties is loaded into forms that each constitute a step-item in a stepper. The forms array that loads all of the forms is 'reset' with this function:
private resetPropForms(): void {
this.propForms = [];
}
This is the function that receives as an argument a properties event and then sets the forms array:
onPropertiesEmitted(properties: Property[]): void {
this.resetPropForms();
this.resetDividedProperties();
this.resetUndividedProperties();
this.setDividedProperties( properties );
this.setForms();
this.setUndividedProperties( properties );
}
And this is what my template looks like:
<se-stepper
*ngIf="undividedProperties"
[linearMode]="false"
[(activeStepIndex)]="activeStepIndex"
>
<se-step-item
*ngFor="let form of propForms"
>
<app-properties-form
[propertiesForm]="form"
(change)="onPropertiesChanged($event)"
>
</app-properties-form>
</se-step-item>
</se-stepper>
Lastly, the view is updated but only after I go to the next step and come back. And if I save the properties then the correct value is sent to the backend even though the value displayed in the view is incorrect/not updated. Any ideas why this is happening. I tried using trackBy with a unique identifier for each form ( using a random number ) but that didn't work. I tried using ChangesRef and detectChanges() and that didn't work. When I leave out the *ngFor and just display the first form in the array it updates properly so that leads me to believe that this problem has something to do with the *ngFor.
edit:I'm pretty sure setTimeout() placed in onPropertiesEmitted() worked but I forgot where I put it to make it work and it seemed like a not-so-great solution to the problem
I Think the problem is that the array is the same and change detect won't detect changes, try this
private resetPropForms(): void {
//this.propForms = [];
//array.slice() returns new array
this.propForms = this.propForms.slice(0,0);
}
You could also reset the form before assigning new properties ( form.reset() )
I have an AngularJS application that manages badges. In the application is a form to set the badge # and the name of the person it is assigned to, etc. This gets stored in $scope.badge.
When the user submits the form, I want to add the new badge to a list of badges, which is displayed below the form.
Partial code looks like this:
var badge = angular.copy($scope.badge); // make a copy so we don't keep adding the same object
$scope.badgeList.push(badge);
The first time I run this code, it adds the badge as expected.
Any subsequent time I run this code, the next badge REPLACES the previous badge in the badgeList. In other words, if I add 5 badges, the badgeList still only has 1 object in it because it just keeps getting replaced.
I'm thinking that this may be happening because the same object keeps getting added? Maybe I'm wrong? I am using angular.copy to try and avoid that happening, but it doesn't seem to be working.
Any thoughts on this?
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
no need to use angular.copy since you are ultimately storing all the badges in an array
angular.copy is used when you want to make a clone of object and not update the existing object and the clone's change are not reflected in main object.
If you just want to maintain a list of badges you can execute this block of code
like this
function addBadges(){
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
}
If you are refreshing the controller then obviously the variable will be reset and for such a case you need to make use of angular services.
Create a service and inside the service you need to define getter and setter method that will help in data persistence
and your bages array if saved in service will persist till the application is in foreground.
You could do something like this.
function addBadges(){
//initialize if undefined or null
if(!$scope.badgeList){
$scope.badgeList = [];
}
//Check if badge does not exists in the list
if ($scope.badgeList.indexOf($scope.badge) === -1) {
//Add to badge list
$scope.badgeList.push($scope.badge);
}
}
I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)