Add editable dynamic objects to an array in Mithriljs - javascript

I have an array, that I need to populate with multiple 'properties', something like this:
[
{
name: 'Date',
value: '27 Oct'
},
{
name: 'Type',
value: 'Image'
},
{
name: 'Client',
value: 'Apple'
}
]
I want to list out all the properties in ul and I want to have a + button, that will add a new object to an array (initially name and value will be blank). After double clicking on each item (li in this case), I want to make these properties editable.
So I did something like this:
var Properties = {};
Properties.items = [
{
name: m.prop('Date'),
value: m.prop('21 Dec'),
editable: m.prop(false)
}
];
This is my initial array.
Then, I have a controller:
Properties.controller = function() {
this.addNewItem = function() {
Properties.items.push({
name: m.prop('Sample'),
value: m.prop('Test'),
editable: m.prop(false)
})
};
this.toggleEditable = function(item) {
item.editable(!item.editable());
console.log(item.editable());
};
};
My view:
Properties.view = function(ctrl) {
return m('ul.list-unstyled', [
Properties.items.map(function(item) {
return m('li', [
item.editable() ? m('input', {
value: item.name(),
onkeyup: function(e) {
if(e.keyCode == 13) {
ctrl.toggleEditable.bind(ctrl, item);
} else {
m.withAttr('value', item.name)
}
}
}) :
m('span', {
ondblclick: ctrl.toggleEditable.bind(ctrl, item)
}, item.name()),
m('span', ': '),
m('span', item.value())
]);
}),
m('div',{
onclick: ctrl.addNewItem.bind(ctrl)
}, '+')
]);
};
And finally:
m.mount(document.querySelector('.container'), Properties);
But when I start typing, it sort of overwrites what I wrote and doesn't save the new value..
Any ideas, anyone?

You should keep your original array as the data model, pass it to Properties and let Properties store only the editable state. That is a nice data separation and will simplify the code. It may fix the save problem as well, since it can just set the array value directly in the onkeyup event.
For the overwrite problem, maybe adding a key attribute will work? It seems related. Check out http://mithril.js.org/mithril.html#dealing-with-focus for more information.

Related

How can I return a dynamic data using methods in JavaScript?

I am having a variable that needs to return data according to the array of objects.
There are 3 dummies which are dynamic in such a way -->
Example:
dummy1_A, dummy1_B, dummy1_C, ...
dummy2_A, dummy2_B, dummy2_C, ...
dummy3_A, dummy3_B, dummy3_C, ...
I want to set 'fieldName' and 'text' while returning.
And this is my code. I have to use JSON.stringify to show the data I need
Also, I am using map method
let a1 = [
{
dummy1_A: 0.5526714707565221,
dummy2_A: 0.5526714707565223,
dummy3_A: 0.5526714707565224,
dummy1_B: 0.5028423429150607,
dummy2_B: 0.5028423429150605,
dummy3_B: 0.5028423429150604,
},
{
dummy1_A: 0.542947572819916,
dummy2_A: 0.4965857885945633,
dummy3_A: 0.4965857885945677,
dummy1_B: 0.4470431086251489,
dummy2_B: 0.3785646261205342,
dummy3_B: 0.3785646261205345,
},
];
let a2 = a1.map((x, i) => {
let seqStr = String(a1.entries); // I need some help here
return {
text: seqStr,
fieldName: seqStr,
};
});
// output
[
{ text: 'dummy1_A', fieldName: 'A' },
{ text: 'dummy2_A', fieldName: 'A' },
{ text: 'dummy1_B', fieldName: 'B' },
{ text: 'dummy2_B', fieldName: 'B' },
];
We can also use forEach but this also need more logic
a1.forEach(obj => {
const key = Object.keys(obj)[0];
const newKey = key[key.length - 1];
obj[newKey] = obj[key];
delete obj[key];
});
I have used console.log(JSON.stringify(a2)); Though I was using map but not got such result
let newHeaders1 = a1.map( i => {
return {
text: i,
fieldName: 'dummy1_'+i,
width: 110
};
});
Use this and create 2 more arrays with 2 and 3 respectively.
Then consolidated all the variable in an array using concat or spread operator

Add dynamic key to set state, react

I have this state
this.state = {
dropdown1: false,
dropdown2: false,
dropdown3: false
}
I want to access to these dropdowns in state using this.setState but the number after 'dropdown' comes from API
onMaca = (ev) => {
this.setState({
dropdown + ev: true
})
}
So I want the key to be dynamic 'dropdown1' for example.
Thanks for your answers
you can access the object property like this object['property name']
onMaca = (ev) => {
this.state['dropdown' + ev]= true;
this.setState({
...this.state
})
}
https://codezup.com/add-dynamic-key-to-object-property-in-javascript/
You can use any of these to set key dynamically. I will try to update the answer with an example in a while for setState.
The state is a JS object, so you can get its keys as usual, like so:
const stateKeys = this.state.keys()
Now you have an array: [ "dropdown1", "dropdown1", "dropdown1" ]
One way to use it would be:
const keysMap = statekeys.map(( item, i ) => return {
key: item,
idx: i,
number: item.replace( /dropdown/, '' )
}
keysMap will look like so: [ { key: 'dropdown1', idx: 0, number "1" }, { key: 'dropdown1', idx: 1, number "2" }, { key: 'dropdown1', idx: 2, number "3" } ]
You can query keysMap for a given dropDownNumber like so:
let k = keysMap.find( kmap => kmap.key = dropDownNumber )
To set the dropdown's state:
this.setState({ k: <whatever> })

Bootstrap-vue - Setting table variant dynamically

So I'm using Bootstrap Vue with this test app. I'm trying to change the variant of a table cell depending on the value of it. Unfortunately, the variant parameter will not take a function, so I'm out of ideas on how to achieve this.
This is my code:
var app = new Vue({
el: '#app',
data: {
items: [], //Will be populated through AJAX
fields: [
{
key: 'Vendedor',
label: 'Vendedor'
},
{
key: 'OBJETIVO',
label: 'Objetivo',
formatter: (value) => { return parseFloat(value).toFixed(2)},
variant: estiloObjetivo //THIS IS NOT WORKING
}
]
},
methods: {
Cargar: function () {
var salesperson = getCookie('salespersonCode');
var url_servicio = 'http://MywebService/';
var self = this;
$.ajax({
type: 'GET',
url: url_servicio + 'ventas/' + salesperson,
dataType: "json", // data type of response
success: function(data){
self.items = data
}
});
},
estiloObjetivo (value) {
if value > 0 //I need my cell variant to change depeding on this value
return 'danger'
else
return 'success'
}
}
})
This is my HTML part:
<div id="app">
<button v-on:click="Cargar">Cargar</button>
<b-table striped hover :fields="fields" :items="items"></b-table>
</div>
Any ideas on how to style a Bootstrap-vue cell dynamically?
This is the way it's done in the docs, it's actually set in the "items" array, but how is this useful in cases like mine where I get the data from a web service?:
{
salesperson: 'John',
Objetivo: 2000,
_cellVariants: { salesperson: 'success', Objetivo: 'danger'}
},
So I guess what I need is a way to set the I need is to set the _cellVariants parameter of each element in the 'items' array.
You likely need a computed property. Computed properties automatically update on changes to the reactive variables that they depend on.
The following example implements a computed property, styledItems, which you must use in place of items in the template. It returns a 1-deep copy of items, i.e. a new array containing a copy of each item, with the extra _cellVariants property added.
new Vue({
data: {
items: [ /* your data here */ ]
},
methods: {
estiloObjetivo: value => (value > 0) ? 'danger' : 'success'
},
computed: {
styledItems() {
return this.data.map(datum =>
Object.assign({}, datum, {
_cellVariants: {
Objetivo: this.estiloObjetivo(datum.Objetivo)
}
})
}
})
If you want to add variant to items you could use a computed property called cptItems and define it as follows:
computed:{
cptItems(){
return this.items.map((item)=>{
let tmp=item;
item.OBJETIVO>0?tmp.variant='danger':tmp.variant='success';
return tmp;
})
}
and use that property inside your template like :
<b-table .... :items="cptItems"></b-table>
I was sure the answers above would solve my own issue but they did not. I found a different way to color table cells: https://github.com/bootstrap-vue/bootstrap-vue/issues/1793
This is aside from using variants to color a table cell. Instead, we utilize tdclass and a function.
<script>
new Vue({
el: '#itemView',
data() {
return {
fields: [
{
key: 'Objetive',
sortable: true,
thClass: 'text-nowrap',
tdClass: (value, key, item) => {
return 'table-' + this.getColor(item);
}
}
],
};
},
methods: {
getColor(item) {
return item.Objetive > 0 ? 'danger' : 'success';
},
},
});
</script>
For my own use-case, I needed to compare two cells of the same row, then apply a class to one.
...
{
key: 'DEMAND_QTY',
sortable: true,
thClass: 'text-nowrap',
tdClass: (value, key, item) => {
return 'table-' + this.demandStatusColor(item);
},
},
{ key: 'TOTAL_DEMAND', sortable: true, thClass: 'text-nowrap' },
],
};
},
methods: {
demandStatusColor(item) {
return item.DEMAND_QTY < item.TOTAL_DEMAND ? 'danger' : 'success';
},
}
...
Perhaps this will help someone, if not OP.
#John answer worked for me. I don't have enough reputation to make comment or useful
tdClass: (type, key, item) => {
switch (type) {
case "value":
return "bg-warning text-white";
break;
case "value":
return "bg-danger text-white";
break;
case "value":
return "bg-info text-white";
break;
default:
break;
}
},

How can I remove and count duplicates in an array of objects? - Javascript

I have an issue trying to transform some data, all the possible solutions that I have found, seems to be "ugly" or not the best ones, so here is the problem.
I have this kind of data:
var myData = [
{
action:{
remove_item: {
text: "delete",
textToDisplay: "Remove"
}
}
},
{
action:{
remove_item: {
text: "delete",
textToDisplay: "Remove"
}
}
},
{
action:{
add_item: {
text: "add",
textToDisplay: "Add"
}
}
}
];
And I need to get something like this at the end:
var myData = [
{
text: "delete",
textToDisplay: "Remove"
count: 2
},
{
text: "add",
textToDisplay: "Add"
count: 1
}
];
If you notice, I have removed the duplicates, added the action key and the count with the number of duplicates.
I hope someone has an interesting solution using ES6 or in a functional way.
Based on the requirement of the structure of myData. One way to accomplish what you want to do using Map to keep count of the objects and make it into an array using Array.from.
var myData=[{action:{remove_item:{text:"delete",textToDisplay:"Remove"}}},{action:{remove_item:{text:"delete",textToDisplay:"Remove"}}},{action:{add_item:{text:"add",textToDisplay:"Add"}}}];
// map to keep track of element
// key : the properties of action (e.g add_item, remove_item)
// value : obj { text, textToDisplay, count }
var map = new Map();
// loop through each object in myData
myData.forEach(data => {
// loop through each properties in data.action
Object.keys(data.action).forEach(d => {
let currKey = JSON.stringify(data.action[d]);
let currValue = map.get(currKey);
// if key exists, increment counter
if (currValue) {
currValue.count += 1;
map.set(currKey, currValue);
} else {
// otherwise, set new key with in new object
let newObj = {
text: data.action[d].text,
textToDisplay: data.action[d].textToDisplay,
count: 1,
}
map.set(currKey, newObj);
}
})
});
// Make an array from map
var res = Array.from(map).map(e => e[1]);
console.log(res);
In my opinion, the key on data.action is redundant, because you are already specifying the actions in text property. So I believe a better data structure for my data is the following:
var myData = [
{
action:{
text: "delete",
textToDisplay: "Remove"
}
},
{
action:{
text: "delete",
textToDisplay: "Remove"
}
},
{
action:{
text: "add",
textToDisplay: "Add"
}
}
];
And if you think that is the case, you can change newObj in my previous code to make it work.
let newObj = {
text: data.action.text,
textToDisplay: data.action.textToDisplay,
count: 1,
}
You can use reduce and map.
This is a very straightforward solution:
var myData = [
{ action: { text: "delete" } },
{ action: { text: "delete" } },
{ action: { text: "add" } }
];
const group = (arr) => {
const reduced = arr.reduce((acc, curr) => {
const text = curr.action.text;
acc[text] = acc[text] || 0;
acc[text] ++;
return acc;
}, {});
return Object.getOwnPropertyNames(reduced).map((prop) => ({ text: prop, count: reduced[prop] }));
};
var grouped = group(myData);
console.log(JSON.stringify(grouped, null, 4));
From the comments I assume this data is possible:
var myData = [
{
action:{
remove_item: {
text: "delete",
textToDisplay: "Remove"
},
edit_item: {
text: "edit",
textToDisplay: "Edit"
}
}
},
{
action:{
remove_item: {
text: "delete",
textToDisplay: "Remove"
},
// note this _key_ is duplicated below (see note at the bottom)
add_item: {
text: "addy",
textToDisplay: "Addy"
}
}
},
{
action:{
add_item: {
text: "add",
textToDisplay: "Add"
}
}
}
];
A possible reducer could look like this:
myData.reduce(function(memo, obj, index) {
let key,
action = obj.action,
entries = Object.entries(action),
entry,
data,
keyIndex;
// action may have multiple fields (see note)
for (entry of entries) {
// the object field name is the key (see note)
// key = entry[0];
// the text attribute is the key (see note)
key = entry[1].text;
// index where in result[] the object is
// already stored (if so)
keyIndex = memo._keyIndex.indexOf(key);
if (keyIndex == -1) {
// key not indexed (and saved) yet
data = {};
Object.assign(data, entry[1], { count: 1 });
memo.result.push(data);
memo._keyIndex.push(key);
} else {
// key already indexed, get the data and
// increment counter
data = memo.result[keyIndex];
data.count++;
}
}
if (index < myData.length - 1) {
// for all items but last memo is an internal wrapper object
return memo;
} else {
// for final item return the actual result
return memo.result;
}
}, { result: [] /* actual result, this is finally returned */, _keyIndex: [] /* temp. index of keys */ });
To understand this code, you should be familar with:
reduce() to transform an array to another data type entry by entry
Object.entries() to get key-value pairs of an object in form of an array
for..of loop to iterate array/object entries
The interesting part here is, that the "memo" object holds two references during the run. This allows tracking duplicate keys in _keyIndex. Only with the last element, the result array is returned from reduce().
Note:
The for..of loop is used to process multiple fields inside the { action: { } } object (from the comments on your question). If not required, processing entries[0] with the code inside the for..of block suffices.
The lines assigning key determine the key that is checked for duplicates.
If the field name in action is the determining key, the first (commented) line would be enough. (This makes "Addy" appear in the results, counted twice.)
If the value of the text field in an action object matters, the second line is required. (This makes "Addy" and "Add" appear in the results.)
Depending on the determining key, the resulting object may still have duplicates, but this code should direct you well enough.
var myData = [
{
action: {
text: "delete",
textToDisplay: "Remove"
},
count: 1
},
{
action: {
text: "delete",
textToDisplay: "Remove"
},
count: 1
},
{
action: {
text: "add",
textToDisplay: "Add"
},
count: 1
}
];
//findIndex method returns index of element passed, returns -1 if element is not in array
let uniqueData = [];
myData.forEach(data => {
let index = uniqueData.findIndex(item=> item.action.text === data.action.text);
if (index === -1) {
uniqueData = [...uniqueData, data];
}
else {
uniqueData[index].count++;
}
})
console.log(uniqueData);

Values and captions in Ace Editor autocomplete

I am using Ace Editor v.1.1.8 with ext-language_tools.
I want to achieve the following behavior with auto-complete:
User starts typing, presses Ctrl+Space and I show him the list of found captions, when he selects one of them the value is inserted.
My code:
var completions = [
{id: 'id1', 'value': 'value1'},
{id: 'id2', 'value': 'value2'}
];
var autoCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {
callback(null, []);
return;
}
callback(
null,
completions.map(function(c) {
return {value: c.id, caption: c.value};
})
);
}
};
editor.setOptions({
enableBasicAutocompletion: [autoCompleter],
enableLiveAutocompletion: false,
enableSnippets: false
});
So what I need from the above is that user enters 'val', sees the list with 'value1' and 'value2', selects one of them and 'id1' or 'id2' is inserted into editor.
At this point:
Editor always searches by value (and i need to search by caption)
If I set 'value' = c.value, then editor will search correctly but will insert c.value while I need c.id inserted.
Here's the working code: http://plnkr.co/edit/8zAZaLpZVhocHmI4GWhd?p=preview
UPD:
Was able to achieve this behavior by adding insertMatch method to data:
completions.map(function(c) {
return {
value: c.name,
meta: c.id,
completer: {
insertMatch: function(editor, data) {
if (editor.completer.completions.filterText) {
var ranges = editor.selection.getAllRanges();
for (var i = 0, range; range = ranges[i]; i++) {
range.start.column -= editor.completer.completions.filterText.length;
editor.session.remove(range);
}
}
editor.execCommand("insertstring", data.meta);
}
}
};
})
You can change
var caption = item.value || item.caption || item.snippet;
line at https://github.com/ajaxorg/ace/blob/v1.1.8/lib/ace/autocomplete.js#L449 to
var caption = item.caption || item.value || item.snippet;
You can also implement custom insertMatch method on completer https://github.com/ajaxorg/ace/blob/v1.1.8/lib/ace/autocomplete.js#L181, but making a pull request with first option is better

Categories

Resources