Vue.js filterBy array as searchText - filter

In vue.js how can one filterBy multiple properties?
Consider following data structure.
new Vue({
el: body,
data: {
items: [
{name: 'thing1', age: 5, properties: [
{ name: 'color', value: 'black'},
{ name: 'weight', value: 3}
]},
{name: 'thing2', age: 3, properties: [
{ name: 'length', value: 4},
{ name: 'weight', value: 4}
]},
{name: 'thing3', age: 9, properties: [
{ name: 'color', value: 'black'},
{ name: 'length', value: 20}
]}
],
property: ''
}
});
Now, one can easily filterBy single property, like so:
<input v-model="property" />
<div v-for="item in items | filterBy property in 'properties'">
{{ item.name }}
</div>
However, if I would like to search by more than one property.
e.g. to get thing3, I have tried this, but, of course, this does not work.
property: ['black', 20]
Do I need to implement some sort of dynamic filterBy chaining? I do not know how many properties there will be.
Crude way to accomplish this would be to create filterBy for each distinct property in current items array and keeping most of them empty. Something like this.
new Vue({
el: body,
data: {
items: [],
property1: '',
property2: '',
property3: '',
...
propertyN: ''
}
});
And this:
<div v-for="item in items | filterBy property1 in 'properties'|
filterBy property2 in 'properties'|
filterBy property3 in 'properties'|
...
filterBy propertyN in 'properties'">
{{ item.name }}
</div>
But that feels wrong to do.

Seems like custom function is the only viable option.
filters: {
myProperty: function(items, properties = this.properties) {
if (!properties || properties.length === 0) {
return items;
}
return this.recursiveFilter(items, propeties, 0);
}
},
methods: {
recursiveFilter: function(items, properties, currentPosition) {
if (currentPosition+1 > properties.length)
return items;
var new_items;
new_items = items.filter(function(item) {
for (row of item.properties) {
if (row.value == properties[currentPosition])
return true;
}
});
return this.recursiveFilter(new_items, properties, currentPosition+1);
}
}

Related

Undefined array key "houseAreaTypeId""

I am trying to store multiple row data in database. But after running foreach loop I am getting this error--Undefined array key "houseAreaTypeId""
my template--
<select
v-model="tab.selectedHouseType"
name="houseAreaTypeId[]"
>
<option
v-for="houseType in houseTypes"
:key="houseType.id"
:value="houseType.id"
>
{{ houseType.name }}
</option>
</select>
script ---
export default {
data() {
return {
tabs: [{
selectedHouseType: "",
rows: [{
selectedDecor: {},
selectedDes: "",
}],
}],
};
},
methods: {
submit(){
axios.post('/api/create-cart', {
myArray: this.tabs
}).then(({data})=>{
this.tabs.selectedHouseType = '',
this.tabs.decorTypes = ''
});
},
laravel controller--
public function createCart(Request $request)
{
if ($tabs = $request->get('myArray')) {
foreach ($tabs as $tab) {
HouseAreaCart::create([
'houseAreaTypeId' => $tab['houseAreaTypeId'],
]);
}
}
return response()->json();
}
can anyone suggest me what's wrong in my code
Where do you expect houseAreaTypeId to be coming from? This is your tabs data property:
tabs: [{
selectedHouseType: "",
rows: [{
selectedDecor: {},
selectedDes: "",
}],
}],
Your available keys then are selectedHouseType and rows. Your select's v-model binds tab.selectedHouseType with the value of houseType.id so maybe you mean to access $tab['selectedHouseType'] which will give you back that houseType ID value?

How to use an object with Vuetify v-select?

Can I use an object for the items prop with Vuetify v-select ?
If yes, how to set the object key as item-text and object value as item-value ?
No. But is easy, you convert the object to an array using the default text and value:
<v-select
v-model="selected"
:items="Object.keys(items_obj).map((key) => ({text:key, value:items_obj[key]}))"
/>
May be better to create a computed property or a method, or even your custom component if you do this a lot
You can use this code to use object in v-select
<template>
<v-select
v-model="select"
:items="items"
item-text="state"
item-value="abbr"
label="Select"
return-object
single-line
></v-select>
</template>
<script>
export default {
data () {
return {
select: { state: 'Florida', abbr: 'FL' },
items: [
{ state: 'Florida', abbr: 'FL' },
{ state: 'Georgia', abbr: 'GA' },
{ state: 'Nebraska', abbr: 'NE' },
{ state: 'California', abbr: 'CA' },
{ state: 'New York', abbr: 'NY' },
],
}
},
}
</script>
Visit this link to read more about it https://vuetifyjs.com/en/components/selects/#custom-text-and-value

beforeChange value is undefined in Knockout when using the mapping plugin

Based on the answer of this question, I try to get the value before change in an observable with the following code.
var phoneBook;
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('newvalue: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug(previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'address#domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'John2',
email: '222address#domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'peter',
email: 'address#domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'almond',
email: '222address#domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}
$(document).ready(function() {
phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
setTimeout(dofunc, 5000)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<div>
<p id="log"></p>
</div>
The change event happens when the mapping plugin is called a again time (here after a timeout of 5 seconds), but the previousValue always comes out as undefined.
What i am doing wrong?
Here is the jsfiddle, too: https://jsfiddle.net/icinema/ungbz27s/1/
The problem here is that you are using the mapping plugin wrong, and that your test data makes no sense.
There will only ever be a "previous" value, when you write a new value to the exact same observable. But the mapping plugin will throw away all your viewmodels and make new ones when you map a completely different set of data.
How is it supposed to know that the object with the name "John" in the first round is supposed to be the same person that has the name "peter" in the second round? It can't. So it throws out all the contacts including all their phone numbers and makes new ones. There never is a "previous" value in this scenario.
What you need is
Give the contacts and phone numbers a key, so they can be identified as the same object across calls to ko.mapping.fromJS.
Tell the mapping plugin which of the object's properties is supposed to be the key, by adding a key function to the mapping configuration.
Read the documentation of the mapping plugin - read the entire thing, it's not that much to begin with.
In the below example I used name as the key for contacts and phoneType as the key for phones, and I amended the test data so that they have the same names and phone types across both sets. You probably want to use a contact ID number as the key instead of the name.
The advantage of using the key function is that knockout will only update the phone number text in the DOM, instead of throwing out and recreating the whole <li> and everything in it, because it can recognize existing viewmodel instances and keep them. This will cut down on rendering time.
/* global ko, $ */
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('new value: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug('previous value: ' + previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
},
key: function (data) {
return ko.unwrap(data.phoneType);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
},
key: function (data) {
return ko.unwrap(data.name);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'john#domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
},
{
name: 'Peter',
email: 'peter#domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'John',
email: 'john#domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
},
{
name: 'Peter',
email: 'peter#domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
}
]
};
$(document).ready(function() {
var phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
debug('<hr>');
setTimeout(function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}, 3000);
});
#log { font-family: monospace; font-size: small; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<hr>
<div id="log"></div>

Multiple sort on Kendo Grid columns / DataSource - set sorting dynamically

What I'm trying to accomplish is to apply an "automatic" secondary column sort when a user sorts a column in a kendo grid.
So in this JS fiddle example, if a user sorts by "Value", it'll also sort by "Name". Note that the 0s are sorted together, but the names aren't alphabetical. I'd like them to be alphabetical (the secondary sort).
Here's an attempt at overriding the datasource sorting to accomplish this. I'm taking the user's original sort and the adding an additional sort on "SortedName". Based on the sorting array that's logged, it seems to be close but is still not working.
Any other ideas on how to accomplish this?
Note: I don't want to allow users to sort by multiple columns. The real world example I'm using this for can have up to 50+ columns (unfortunately), so multiple sort can get confusing / unintuitive. And I'd like it to be done behind the scenes without extra user interaction.
Example code for overriding kendo datasource sort():
dataSource.originalSort = dataSource.sort;
dataSource.sort = function () {
// take the user's sort and apply sorting on an additional column
// the sort array should look like this:
[
{ field: "Value", dir: "asc" }, // this is what the user sorted by
{ field: "SortedName", dir: "asc" }, // and I'm adding this
]
return dataSource.originalSort.apply(this, arguments);
}
Please try with the below code snippet.
<div id="grid">
</div>
<script>
var dataSource = new kendo.data.DataSource({
data: [
{ Name: "Lisa", Value: 1 },
{ Name: "Dan", Value: 12 },
{ Name: "Ken", Value: 5 },
{ Name: "Arthur", Value: 15 },
{ Name: "Bob", Value: 0 },
{ Name: "Sally", Value: 0 },
{ Name: "Alexis", Value: 0 },
{ Name: "Cody", Value: 0 },
{ Name: "Steve", Value: 0 },
{ Name: "Andrew", Value: 0 },
{ Name: "Duke", Value: 0 }
],
schema: {
model: {
fields: {
Name: { type: "string" },
Value: { type: "number" }
}
}
}
});
$("#grid").kendoGrid({
dataSource: dataSource,
dataBound: function (e) {
var isSortedByName = false;
var grid = $("#grid").data("kendoGrid");
var ds = grid.dataSource;
var sort = ds.sort();
if (sort) {
for (var i = 0; i < sort.length; i++) {
if (sort[i].field == "Name") {
isSortedByName = true;
}
}
if (isSortedByName == false) {
sort.push({ field: "Name", dir: "asc" });
ds.sort(sort);
}
}
},
columns: [
{ field: "Name" },
{ field: "Value" }
],
sortable: true
});
</script>

YUI3 DataTable add two rows

I am trying to modify the the YUI dataTable to add rows dynamically after the dataTable is created but instead of adding one new row it adds two.
My code:
var index = 0;
YUI({
filter: 'RAW'
}).use([
'node',
'io',
'datasource',
'json-parse',
'json-stringify',
'datatable',
'datatable-sort',
'datatable-mutable',
'datatable-scroll',
'datatable-paginator',
'model-list'
],
function(Y) {
var dataTableColumns = [
{
key: 'id',
label: '#'
},
{
key: 'name',
label: 'Name',
allowHTML: true,
formatter: function (valueObject) {
return valueObject.name;
}
}
];
var dataTable = new Y.DataTable({
width: "100%",
columns: dataTableColumns,
plugins: [{
cfg: {
highlightRange: false
},
fn: Y.Plugin.DataTableHighlight
}]
}
);
dataTable.render('#dataTableDiv');
var dataSource = new Y.DataSource.IO({
source: "/localhost/data/"
});
dataSource.plug({fn: Y.Plugin.DataSourceJSONSchema, cfg: {
schema: {
resultFields: [
{key: "id"},
{key: "name"}
]
}
}});
dataTable.plug(Y.Plugin.DataTableDataSource, {
datasource: dataSource
});
dataTable.datasource.load();
Y.one("#addButton").on('click', function (event) {
index++;
var rowData = {
id: index,
name: "Test row"
};
dataTable.addRow(rowData, {index: 0});
});
}
);
So After DataTable is created I have:
And after dataTable.addRow(rowData, {index: 0}); I have:

Resources