Knockout mapping grid with dynamic adding of items not behaving - javascript

I have a nice knockout viewmodel with a list that is shown as a grid.
I do use the mapping plugin.
You can select items, reverse selection, remove items from the list and dynamically add items.
But if i do add an item, I can't remove it or any other items.
Also if I add another one it does not show.
But a computed value does show extra lines
Here is the fiddle isolating the issue:
http://jsfiddle.net/medo/7jrnb/
self.add = function () {
self.orderLines.push(ko.mapping.fromJS({ Sku: "", Qty: 1, Name: "", OriginalSKU: "", Description: "", Exists: false, Selected:false }));
};
delete and select items, all is well.
Press add item and deleting does not work.
pressing add again is also an unexpected result.
knockout-2.2.1.js
Knockout Mapping plugin v2.4.1

In your add function, you misspelled OriginalSku as OriginalSKU (different case). This causes the binding to fail and, it appears, to stop knockout from working. Fix your spelling and you're all set.

Related

Shopware 6 <sw-media-list-selection-v2> component not updating after media selection

I'm working on slider CMS element where every slide has image gallery, for selecting images I use <sw-media-list-selection-v2> and <sw-media-modal-v2> components. Each slide is created as <sw-collapse>. In my config/index.js I keep object, 'mediaItems', where key is slide ID and property is array of image entities selected by mentioned components.
The problem is: while selected images are being added to my object property they are not displayed in component, when I collapse and expand slide then selected images are displayed but not immediately after being selected.
I think problem is that Vue "don't know" that array bound to object property was changed but I don't know how to make it work.
In default Shopware 6 image slider array of images, declared in data(), is directly set to <sw-media-list-selection-v2> and it works fine by in my case array is in object and it's not working as I would like it to.
<sw-media-list-selection-v2
:entity-media-items="mediaItems[sliderItem.id]"
:entity="entity"
:upload-tag="uploadTag"
:default-folder-name="defaultFolderName"
#upload-finish="onImageUpload($event, sliderItem)"
#item-remove="onItemRemove($event, sliderItem)"
#open-sidebar="onOpenMediaModal"
/>
data() {
return {
initialFolderId: null,
entity: this.element,
mediaItems: {},
mediaModalOpen: false
};
},
onMediaSelectionChange(mediaItems, sliderItem) {
mediaItems.forEach(item => {
sliderItem.mediaItems.push({
mediaId: item.id,
mediaUrl: item.url
});
});
this.mediaItems[sliderItem.id].push(...mediaItems);
this.emitUpdateEl();
},
emitUpdateEl() {
this.$emit('element-update', this.element);
},
If I understand your problem correctly it is caused by the mediaItems property not being mutated reactively. The component then won't "know" about the changes unless it is re-rendered. Try using the reactive vue setter instead:
this.$set(this.mediaItems, sliderItem.id, [...this.mediaItems[sliderItem.id], ...mediaItems]);

How to delete a specific textfield inside a table onclick of a button using reactjs & material ui

I want to delete textfield on a specific row inside a table whenever the specific row delete button is clicked, currently I am able to add text field onclick of add button and I'm able to delete the textField onclick of remove button but whenever the remove button is clicked all the textfields are getting deleted instead of the specific textfield Also the add icon needs to be next to the last text field but currently I could only achieve it having it on the 1st text field. Please someone help me out here. Image of what i have achieved so far.
As you can see I'm trying to delete textfields on 1st row ,but it is automatically deleting textfields in every row of table.
Working codesandbox: https://codesandbox.io/s/heuristic-fermat-63ixw?file=/src/App.js
Can someone please helpme out
Issue
You are adding multiple custom rows all with the same rowId, so when you filter them you are removing more than you want.
You also incorrectly compare a newRow.rowId against this.state.customRow.rowId which is of course undefined since it's an array, and update your state to nest customRow into another array.
handlingDeleteRow = (index, newRow) => {
let newdel = this.state.customRow.filter(
(newRow) => newRow.rowId !== this.state.customRow.rowId // <-- incorrect comparison
);
this.setState({
customRow: [newdel] // <-- nest array in array
});
console.log(this.state.customRow);
};
Solution
I suggest adding a guid to your added custom rows and matching on that.
import { v4 as uuidV4 } from 'uuid';
...
addCustomFile = (index) => {
this.setState({
resError: null,
customRow: [
...this.state.customRow,
{ rowId: index, fileData: false, fileName: false, guid: uuidV4() }
]
});
};
When deleting use the new guid property.
handlingDeleteRow = (index, newRow) => {
let newdel = this.state.customRow.filter(
(row) => row.guid !== newRow.guid
);
this.setState({
customRow: newdel
});
};
Demo
I read your code, and it is working fine.
One thing you would like to add is the index of text inputs in a single row.
Problem
Currently, if user presses + button three times, addCustomFile will add three elements to state.customRow. However, three of these elements are identical, and there is nothing to distinguished from each other.
That is why the filter function (which is executed when X button is pressed) will remove all the elements inside state.customRow that belongs to same row.
Solution
I suggest you to add a new attribute to elements of state.customRow to distinguish added inputs of a same row. For example, it could be insideRowIndex and each added input can have incrementing integer (0,1,2,3,4 ...).
Checking insideRowIndex inside the filter function will let you to only remove the input you intend to, not all the others that belong to same function.

Why is gridstack.js swapping my elements around when vue.js removes a node

I am trying to setup gridstack.js to work with my vue components as it does in this knockout example: https://github.com/troolee/gridstack.js#use-with-knockoutjs/.
Here is a codepen of what I have: https://codepen.io/nyoung697/pen/BperoZ
Add 3 textboxes to the form by clicking 'textbox' 3 times.
Delete the top textbox by hovering overtop and clicking the trash can.
Deleting them from the top down works fine (so it seems). The newest element is at the top, as gridstack is adding it to the top. So this is the last element in the array.
Now try resizing the textboxes and deleting them in a random order.
Gridstack is getting confused and swapping elements around on the page. I have no idea why it is doing this. Can someone please help me figure this out? I have been going around in circles for weeks trying so many different things.
I added some console logging to show what id is being referenced. When you delete the elements in order of the last one added, the id in the "destroyed" method is the same as all the other methods/hooks being hit. However if you try to delete them in a random order, the id that is being printed in the destroyed method is different.
destroyed:
Vue.component('ntextbox', {
template: '#textboxtemplate',
props: ['component'],
created () {
console.log('created textbox control');
},
methods: {
removeWidget: function(){
console.log('child remove: ' + $(this.$el).attr('id'));
this.$emit('remove');
}
},
destroyed () {
console.log('textbox control destroyed');
var grid = $('.grid-stack').data('gridstack');
console.log(grid);
console.log($(this.$el).attr('id'));
grid.removeWidget(this.$el);
//console.log(grid);
}
});

Angular Material md-chips not refreshing correctly

I am developing an app that works with a "Widget layout".
The user gets a page which is a Dashboard with different widgets he can interact with, such as a table, graphs etc.
I am adding a global "filter" using <md-chips>, the objective of this is having filters that are shared by all the widgets and can be updated by all the widgets.
My Filter list uses <md-chips>, with read-only and md-removable set to True. So filters can only be deleted, or added by interacting with the widgets (so only added programmatically).
One of the feature of this "module" is to add a new filter on a field when a Graph element is clicked.
Example :
Here is my Filters list before clicking a Graph element
Now I click on a Graph element which is in a Child controller of my Filter controller, it will $emit an event to say to the global controller : "I want to update the filters ! here is the filter to add"
And the Filter Controller will get that event and update his filters accordingly ($scope.tags is the list of filters and the model used in <md-chips>)
// CHIPS
$scope.tags = [];
$scope.readOnly = true;
$scope.removable = true;
// FILTER LISTENER
$scope.$on('filterChildChanged', function (event, filter) {
$scope.tags.push(filter);
console.log($scope.tags);
console.log("Parent event fired !");
});
At that point I would expect the <md-chips>element to refresh, because $scope.tags just got a new filter :
<md-chips
ng-model="tags"
readonly="readOnly"
md-removable="removable"
id="filters">
<md-chip-template>{{$chip.value}}</md-chip-template>
</md-chips>
But instead, nothing happens! and the weird part is, it refreshes when I click on one of the existing chip (I had a "test" chip) !
TESTS :
So when I push a test chip on the array before rendering :
$scope.tags.push({field: "testField", value: "test"});
And click on a bunch of Graph elements, $scope.tags is perfectly updated, BUT the visual stays the same until I select the chip "test", then all the <md-chips> appear just like it triggered some refresh function.
Any hint on why the <md-chips> element is not refreshed as $scope.tags (its model) is updated BUT is updated when a chip is selected ?
I tried to trigger md-on-select to force this behavior to happen every time I add a new filter to $scope.tags but I got no luck so far !
Thanks for reading !
NOTE : I am using the latest version of Angular MATERIAL (HEAD MASTER) see doc here : https://material.angularjs.org/HEAD/ | built from the GitHub repository.
EDIT : The problem comes from $$hashKey not being added to the objects, they are only added when I click on one of the existing tags, I need to find a way to add this $$hashKey attribute when I add a new filter
The problem came from the absence of $$hashKey when I added a new filter to my filters model for my chips ($scope.tags).
So I just needed to change
$scope.tags.push(filter);
to
$scope.$apply(function () {
$scope.tags.push(filter);
});
And here is my console.log test
Good thing I learned :
the <md-chips> directive only knows it needs to be updated if its <chip> objects have a $$hashKey
See this on scope.$apply

jQuery Validate, Select2, and Bootstrap 3 Popovers - How to Bind Popover To Select2's Parent Elements Instead of Hidden Select Element

I've implemented the excellent method to use Bootstrap 3's Popovers to show validation error messages, and for the most part it works well - except when it comes to hidden or replaced elements, like Select2 or CKEditor. The popover positions itself correctly on regular elements like input and normal selects, but not a select enhanced by Select 2.
I've created a Fiddle that Sparky fixed so it actually works: http://jsfiddle.net/jemxtthb/4/
$("#noticeSentTo").select2();
var validator = $("#documentAdmin").validate({
debug: true,
ignore: "",
showErrors: function(errorMap, errorList) {
$.each(this.successList, function(index, value) {
return $(value).popover("hide").parents(".form-group").removeClass('has-error').addClass('has-success');
});
return $.each(errorList, function(index, value) {
var _popover;
_popover = $(value.element).popover({
trigger: "manual",
placement: "auto top",
content: value.message,
container: "body",
template: "<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><div class=\"popover-content\"><p></p></div></div>"
});
_popover.data("bs.popover").options.content = value.message;
return $(value.element).popover("show").parents(".form-group").addClass('has-error').removeClass('has-success');
});
}
});
});
Here's a screen capture of what I see within my application:
I've tried using the selector option on the popovers, but can't figure out how to specify the current element the popover's being applied to (if that's even the right way to do it).
As you can see from my screen capture, the popover appears up and to the far left of the element (in red) it's supposed to be bound to because Select2 is hiding the original select.
UPDATE: Based on #Sparky's suggestion, I've changed it to use errorPlacement instead of the method I used previously - the only issue now is getting the condition where if it's a hidden element (like Select2 or CKEditor) to provide the Popup to the parent object instead of the element itself (since Popovers cannot work on hidden elements, apparently) - I've put in some debug console.log code just to make sure the if statement in the jQuery is firing correctly:
var validator = $("#documentAdmin").validate({
ignore: [],
errorPlacement: function (error, element) {
var lastError = $(element).data('lastError'),
newError = $(error).text();
$(element).data('lastError', newError);
if (newError !== '' && newError !== lastError) {
$(element).popover({
trigger: "manual",
placement: "auto top",
content: newError,
container: "body",
template: "<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><div class=\"popover-content\"><p></p></div></div>"
});
if (element.is(':hidden')) {
// $(element).next().parents(".form-group").addClass('has-error').removeClass('has-success').popover("show");
$(element).next('span').popover('show').addClass('has-error').removeClass('has-success');
console.log('hidden element');
} else {
$(element).popover("show").parents(".form-group").addClass('has-error').removeClass('has-success');
console.log('normal element');
}
}
},
success: function (label, element) {
$(element).popover("hide").parents(".form-group").removeClass('has-error').addClass('has-success');
}
});
And here's an updated Fiddle: http://jsfiddle.net/jemxtthb/6/
OK - really odd - the Fiddle above actually works as expected! Now I need to figure out why it works in the Fiddle and not my app...
Any help/guidance is appreciated!
Looking at the errors in the JavaScript console and fixing the following in your jsFiddle...
There is no point in having a select element with validation if it comes pre-loaded with a value. In other words, there is nothing to validate if every option already contains a value or there is an option with a value that has the selected attribute. Typically, the first option contains value="".
<option value="">Please select<option>
There was a 404 error on your Bootstrap script file. The CDN URL was mangled.
Not critical, but it should be ignore: [], not ignore: "".
You're getting a "no 'name' assigned" error in the debug console because the hidden input element created by Select2 does not contain a name attribute. It's apparently not an issue now, but it could be an issue when you have more elements on the page. The jQuery Validate plugin mandates that all elements to be validated contain a name attribute.
DEMO: http://jsfiddle.net/jemxtthb/4/
for the most part it works well - except when it comes to hidden or replaced elements, like Select2 or CKEditor. The popover positions itself correctly on regular elements like input and normal selects, but not a select enhanced by Select 2.
You should not use showErrors for tooltips because this callback function is typically used for generating a list of all messages, like for a summary at the top of the form. "Gets the map of errors as the first argument and an array of errors as the second."
Instead, you'll need to integrate your tooltips plugin with jQuery Validate by using its errorPlacement and success callback functions; which will put the single pending error message inside a single tooltip. This integration also depends on the available options of your popovers plugin... like can you dynamically change the text inside a tooltip? Can you dynamically/programmatically show/hide them, etc?
Then within errorPlacement you can conditionally target certain elements to tweak the placement when the element is hidden by Select2, etc.
Something more like this...
$("#documentAdmin").validate({
ignore: [],
errorPlacement: function (error, element) {
// put text of error into tooltip with $(error).text()
// conditional placement of the tooltip
// show the tooltip
},
success: function (label, element) {
// hide the tooltip
},
// any other options
});
I'm using the ToolTipster jQuery plugin like this.
As it turns out, the above code worked correctly from the beginning - after #Sparky fixed my Fiddle, the popover worked correctly, positioning itself in relation to the Select2's parent element.
What benefit came out of this was #Sparky's suggestion to use errorPlacement versus the method I was originally using, which gives more control over individual elements' error message placement methods.
I am currently investigating (and suspect) the culprit is an offcanvas plugin I'm using for a sidebar - I didn't include this in the Fiddle because I wanted to keep the Fiddle as concise as possible to the issue and not complicate things. I'll update this when I find out what the problem is. If anyone's interested in implementing this method for validation errors in Bootstrap 3 Popovers, I would use the code in the second Fiddle I posted.

Categories

Resources