Knockout.js: populate drop-down value with data in edit mode - asp.net-mvc-4

I am using knockout.js.
I am having a situation like,
Search page will produce search results .with edit icon and user clicked on edit icon and navigate to edit page.
On Edit page,I have drop down which should be populate with value from the previous screen.
Here is my Code:
var vm= {
Id: ko.observable(),
Name: ko.observable(),
description: ko.observable(),
Type: ko.observable(),
TypeList: ko.observableArray(),
};
var getData = function (Id) {
var ajaxOptions = {
url: 'Api/Group/Get?Id=' +Id,
type: 'GET',
dataType: 'json'
};
function gotData(data) {
vm.UserGroupId(data.Id);
vm.Name(data.name);
vm.description(data.description);
vm.Type(data.Type);
return data;
}
function getTypes(Data) {
//this will fetch me an array of (' ',TypeA,TypeB,TypeC)
dataService.getTypes(vm.TypeList);
//remove the empty one
vm.TypeList.shift();
//try to populate the value which i got from the search result at the top
vm.TypeList.unshift({ "name": 'TypeA', "value": '3' });
}
return $.ajax(ajaxOptions).then(gotUserGroup).then(getTypes);
};
now the issue I am facing is,I am able to get the dropdown values with value from search result.But apart from getting the value from search results,Iam also getting duplicate values.
my Html Code:
<div class="span2 control-label">Type: </div>
<div class="span4 controls form-inline">
<select name="select" data-bind="options: TypeList, optionsText: 'name', optionsValue: 'value', value: Type" />
</div>
For Example:
By default dataservice will give me ('',Type A,TypeB,Typec)
Once I selected the value in search result,lets suppose I select 'TypeA'
On the UI, I am able to see the values as (TypeA,TypeA,TypeB,Typec).
How can I eliminate duplicates?or How can I acheive this functinlity.

From the Knockout documentation: (http://knockoutjs.com/documentation/observableArrays.html)
myObservableArray.unshift('Some new value') inserts a new item at the beginning of the array
So you're adding a new value to the first of the array and leaving the value that was already there. So you need to also remove the value that was already there.
var newItem = { "name": 'TypeA', "value": '3' };
vm.TypeList.remove(newItem);
vm.TypeList.unshift(newItem);

I got the solution like
Initially get the value from db and then match the value with list already available and then remove the selected type and add the selected to the top by using knockoutjs unshift
var selectedType = ko.utils.arrayFirst(vm.TypeList(), function (item) {
if (data.Type === item.name)
return item;
return null;
});
vm.TypeList.shift();
vm.TypeList.remove(selectedType );
vm.TypeList.unshift(selectedType );

Related

VUEJS - Append <tr> element when button pressed

I have a table in my Vuejs project similar to the one that I shared its screenshot above.
My question is; How can I delete a <tr> element from the table when the Delete button of it is pressed for each row?
On the other hand, I want the Add button at the top right of the table to be functional as well. When I click the Add button, I want another <tr> element to be created at the bottom of the table. However, there should be input fields on this element where the user can enter information for each column. After the user has written the information in each column, that row should be added to the table. How can I do that?
if I am correct you are using v-for loop to render all <tr>s
then use the v-for with index like v-for="(obj, index) in objects" to obtain index
to add use Array.prototype.push() to add empty row e.g. objects.push({x: null, y: null})
to remove use Array.prototype.splice() e.g objects.splice(index, 1)
just assign those functionalities to respective buttons.
You could attempt this using a data property in your component and then implement two methods: deleteRow and addRow.
This could be an example code
data() {
return {
dataTable: [
{
id: 1,
code: code1,
description: description1,
},
{
id: 2,
code: code2,
description: description3,
},
...
]
}
}
methods: {
deleteRow(id) {
this.dataTable = this.dataTable.splice(
this.dataTable.findIndex(item => item.id === id)
)
}
addRow(newRow) {
// newRow = {id: 3, code: code3, description: description3}
this.dataTable = [...this.dataTable, newRow]
}
},
Your component will be updated by Vue.js reactivity system.
Probably addRow method should be called from a modal component where you put the input fields to set newRow.

I need help in calling watcher in Vuejs2 when looping the objects in HTML

I am adding one object when clicking on button and displaying the same in HTML. User can able to select the drop down values in options (string or number). Based on the input, need to disable or enable the next text input field. Here is my HTML code,
<table>
<tr><button #click="add_new_input()">Add </button></tr>
<tr v-for="(key, index) in NewArr" v-bind:key=value>
<td>
<multiselect
v-model="key.name"
:options="NameList"
selectLabel='select'
#input="userInput(value)"
></multiselect>
</td>
<td class="modify-td-padding__multi">
<input type="text"
v-model="key.value"
:disabled="isNumber"
class="input-increase-height">
</td>
</tr>
</table>
if we change the key.name dropdown, it will call one function userInput() using #input. passing value will be either "string" or "number". Vue Mehods is below,
userInput: function (value) {
this.getInputType(value);
},
getInputType: function (value) {
if(value === "string") {
this.isNumber = false;
} else {
this.isNumber = true;
}
},
add_new_input: function () {
let vm = this;
vm.NewArr.push({
name: '',
value: '',
});
vm.$set(vm.NewArr, vm.name, vm.value);
}
add_new_input will add new object to NewArr, getInputType function will check the value is "string" or "number". If it is "string", text field should be disabled else enabled.
My issue is, if there are two rows, and if i am selecting key.name for 2nd row, it is affecting the first row input field also(key.name for both rows getting enabled or disabled). I need to make change only the specific text field. So, all the text fields becoming disabled even it is "number".
This is my first project in VueJS. Thanks a lot if anyone helps me on this. Thanks in advance.
You need to manage isNumber per key, so not just
data() {return {isNumber: false}}
But:
#input="userInput(key.name, value)"
:disabled="isNumber[key.name]"
data(){ return { isNumber: {} }}
...
onUserInput: function (key, value) {
this.setIsNumber(key, value);
},
setIsNumber: function (key, value) {
this.$set(this.isNumber, key, value !== "string");
},

Vuejs2 - How to update the whole object from the server not losing reactivity?

I have a list of objects that can be updated from the database.
So, when I load the list, objects have only id and name.
When I click on an object I load other fields that can be of any length - that's why I don't load them with the objects in the list.
I found that when I update an object it can be difficult to keep reactivity https://v2.vuejs.org/v2/guide/reactivity.html so I need to find some workaround.
this code works almost okay:
axios.get('/api/news', item.id).then(function(response){
if (response){
Object.assign(item, response.data.item);
}
});
But the problem is the fields that have not been presented from the beginning is not 100% reactive anymore. What is going on is a new field has been updated reactively only when I change another, previous one. So, if I show 2 text field with old and new properties, if I change the second property, the field will not be updated until I change the first one.
I got item object from the component:
data () {
return {
items: [],
}
},
and
<div v-for="item in items" #click="selectItem(item)" >
<span>{{item.name}}</span>
</div>
Then item's been passed to the function selectItem.
What is the proper pattern to load new fields and keep them reactive? (NB: it's not the case when I can assign field by field - I want to reuse the same code no matter which object it is, so I need so the solution for updating an object at a time without listing all new fields.)
Note. This code works inside the component.
Completely revised post: Ok, the example you give uses an array, which has its own caveats, in particular that you can't directly set values like vm.items[indexOfItem] = newValue and have it react.
So you have to use Vue.set with the array as the first argument and the index as the second. Here's an example that adds a name property to object items and then uses Vue.set to set the item to a new object created by Object.assign.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
other: 'data'
}, {
id: 2,
other: 'thingy'
}]
},
methods: {
selectItem(parent, key) {
const newObj = Object.assign({}, parent[key], {
name: 'some new name'
});
Vue.set(parent, key, newObj);
setTimeout(() => {parent[key].name = 'Look, reactive!';}, 1500);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item, index in items" #click="selectItem(items, index)">
<span>{{item.name || 'empty'}}</span>
</div>
<pre>
{{JSON.stringify(items, null, 2)}}
</pre>
</div>
Have a look at Change-Detection-Caveats Vue cannot detect property addition or deletion if you use "normal assign" methods.
You must use Vue.set(object, key, value)
Try something like the following:
axios.get('/api/news', item.id).then(function(response){
if (response){
let item = {}
Vue.set(item, 'data', response.data.item)
}
});
Than item.data would than be reactiv.
Can simply use Vue.set to update this.item reactively
axios.get('/api/news', item.id).then(function(response){
if (response){
this.$set(this, "item", response.data.item);
}
});

Dojo: Select of empty value for FilteringSelect while required=false

Please look on this code:
dojo.require('dijit.form.FilteringSelect');
dojo.require('dojo.store.JsonRest');
dojo.declare('JsonFilteringSelect', dijit.form.FilteringSelect, {
constructor: function (options) {
dojo.declare.safeMixin(this, options);
if (this.url) {
this.store = new dojo.store.JsonRest({
target: this.url
});
} else {
console.log('JsonFilteringSelect: options.url is not defined');
}
}
});
var getPersonJsonFilteringSelect = new JsonFilteringSelect({
url: '/person/get',
name: 'Test',
title: 'Test title',
required: false,
autoComplete:false,
value: '',
pageSize:10,
queryExpr:'${0}'
}, dojo.byId('select'));
getPersonJsonFilteringSelect.startup();
});
Use case: Suppose I have 20 results into my FilteringSelect.
User selects 1 value of FilteringSelect.
This value set as value of
FilteringSelect.
But after user decides to change this value on
empty value.
As I understand, because required:false FilteringSelect should allow
to set empty value, but it is not. I observe this behavior here:
User clicks FilteringSelect textbox
User clears it
While user presses "Tab" or clicks by other element - FilteringSelect automatically selects first value.
How could I allow user to set empty value into FilteringSelect?
You should add an empty entry ("" or null maybe? I know "" works) to your data store after it's loaded (I'd put it at the beginning) but before startup of the widget.
The "required" issue is strange with FilteringSelect because it won't let you select any arbitrary value -- it has to be an entry from the data store. Yet, if it's not required shouldn't it not care?... Dojo is strange sometimes.

Pass data-attribute value of clicked element to ajax settings

For an implementation of Magnific Popup, I need to pass a post id to the ajax settings. The post id is stored in a data attribute of the element to which Magnific Popup is bound. I would like this to work:
html element:
<a data-id="412">Clicke me</a>
Javascript:
$('.element a').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST',
data: ({
action:'theme_post_example',
id: postId
})
}
}
});
Where postId is read from the data attribute.
Thanks in advance.
$('.element a').magnificPopup({
callbacks: {
elementParse: function(item){
postData = {
action :'theme_post_example',
id : $(item.el[0]).attr('data-id')
}
var mp = $.magnificPopup.instance;
mp.st.ajax.settings.data = postData;
}
},
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST'
}
}
});
Here is how to do it:
html:
<a class="modal" data-id="412" data-action="theme_post_example">Click me</a>
jquery:
$('a.modal').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url : php_array.admin_ajax,
dataType : 'json'
}
},
callbacks: {
elementParse: function() {
this.st.ajax.settings.data = {
action : this.st.el.attr('data-action'),
id : this.st.el.attr('data-id')
}
}
},
parseAjax: function( response )
{
response.data = response.data.html;
}
});
php
function theme_post_example()
{
$id = isset( $_GET['id'] ) ? $_GET['id'] : false;
$html = '<div class="white-popup mfp-with-anim">';
/**
* generate your $html code here ...
*/
$html .= '</div>';
echo json_encode( array( "html" => $html ) );
die();
}
As this answer was the original question regarding inserting data into Magnific's ajax call, I'll post this here.
After many hours of trying to figure this out, you should know that if you're using a gallery with the ability to move between gallery items without closing the popup, using elementParse to set your AJAX data will fail when you visit an item after already viewing it (while the popup is still open).
This is because elementParse is wrapped up in a check that it makes detect if an item has already been 'parsed'. Here's a small explanation as to what happens:
Open gallery at item index 2.
Item has not been parsed yet, so it sets the parsed flag to true and runs the elementParse callback (in that order). Your callback sets the ajax options to fetch this item's data, all is well.
Move (right) to item index 3.
Same as above. The item has not been parsed, so it runs the callback. Your callback sets the data. It works.
Move (left) back to item index 2.
This time the item has been parsed. It skips re-parsing the item's element for assumed potential performance reasons.Your callback is not executed. Magnific's ajax data settings will remain the same as if it were item index 3.
The AJAX call is executed with the old settings, it returns with item index 3's data instead, which is rendered to the user. Magnific will believe it is on index 2, but it is rendering index 3's data.
To resolve this, you need to hook onto a callback which is always executed pre-ajax call, like beforeChange.
The main difference is that the current item isn't passed through into the callback. Fortunately, at this point, magnific has updated their pointers to the correct index. You need to fetch the current item's element by using:
var data = {}; // Your key-value data object for jQuery's $.ajax call.
// For non-closures, you can reference mfp's instance using
// $.magnificPopup.instance instead of 'this'.
// e.g.
// var mfp = $.magnificPopup.instance;
// var itemElement = mfp.items[mfp.index].el;
var itemElement = this.items[this.index].el;
// Set the ajax data settings directly.
if(typeof this.st.ajax.settings !== 'object') {
this.st.ajax.settings = {};
}
this.st.ajax.settings.data = data;
This answer can also be used as a suitable alternative to the currently highest voted, as it will work either way.
You may use open public method to open popup dynamically http://dimsemenov.com/plugins/magnific-popup/documentation.html#public_methods
postId = $(this).attr('data-id')
$(this) retrieve the current element (the link you clicked on), and attr the value of the specified attribute.