Although I have been searching high and low to find the answer to this question, I have not been able to find it. Should there be a well-hidden corner of the internet that I have not searched that does contain the answer to this question, please let me know.
Anyway, on the Dynatable website there is an example to query a table for a certain value. If your input html is indeed a table, this seems to work, but if you're using a stylized listthis produces the following error in my case:
Uncaught TypeError: Cannot read property 'length' of null
Which traces back to line 1582 of the dynatable.jquery.js file:
// Find an object in an array of objects by attributes.
// E.g. find object with {id: 'hi', name: 'there'} in an array of objects
findObjectInArray: function(array, objectAttr) {
var _this = this,
foundObject;
for (var i = 0, len = array.length; i < len; i++) {
var item = array[i];
// For each object in array, test to make sure all attributes in objectAttr match
if (_this.allMatch(item, objectAttr, function(item, key, value) { return item[key] == value; })) {
foundObject = item;
break;
}
}
The following it my select element:
<select id="filter-prop" name="prop">
<option>All</option>
<option>First Run</option>
<option>Rejected</option>
</select>
And the dynatable conversion of my list:
// Function that renders the list items from our records
function ulWriter(rowIndex, record, columns, cellWriter) {
var cssClass = record.prop, li;
if (rowIndex % 3 === 0) { cssClass += ' first'; }
li = '<li class="' + cssClass + '"><div class="thumbnail"><div class="thumbnail-image">' + record.thumbnail + '</div><div class="caption">' + record.caption + '</div></div></li>';
return li;
}
// Function that creates our records from the DOM when the page is loaded
function ulReader(index, li, record) {
var $li = $(li),
$caption = $li.find('.caption');
record.thumbnail = $li.find('.thumbnail-image').html();
record.caption = $caption.html();
record.label = $caption.find('h3').text();
record.description = $caption.find('p').text();
record.color = $li.data('color');
record.prop = $li.attr('class');
}
var pictable = $('#picturelist').dynatable({
table: {
bodyRowSelector: 'li'
},
features :{
recordCount: false,
sorting: false,
},
dataset: {
perPageDefault: 10,
perPageOptions: [5, 10, 15, 20]
},
writers: {
_rowWriter: ulWriter
},
readers: {
_rowReader: ulReader
},
params: {
records: 'objects'
}
}).data('dynatable');
$('#filter-prop').change( function() {
var value = $(this).val();
if (value === "All") {
pictable.queries.remove("Prop");
} else if (value === "First Run") {
pictable.queries.add("Prop","span4 firstrun rejected");
pictable.queries.add("Prop","span4 firstrun");
} else if (value === "Rejected") {
pictable.queries.add("Prop","span4 firstrun rejected");
pictable.queries.add("Prop","span4 rejected");
};
pictable.process();
});
I have not changed much from the examples of the website. The most I did was merge the list example with the query example. Again, if I apply a similar method to a table, it seems to work, but for my list it doesn't. What is going wrong?
The docs weren't very clear here, but I solved the same problem for myself earlier today. Here is your code in working order:
var pictable = $('#picturelist')
.bind('dynatable:init', function(e, dynatable) {
dynatable.queries.functions['prop'] = function(record, queryValue) {
if ( record.prop == queryValue )
{
return true;
}
else
{
return false;
}
};
})
.dynatable({
table: {
bodyRowSelector: 'li'
},
features :{
recordCount: false,
sorting: false
},
dataset: {
perPageDefault: 10,
perPageOptions: [5, 10, 15, 20]
},
writers: {
_rowWriter: ulWriter
},
readers: {
_rowReader: ulReader
},
params: {
records: 'objects'
}
}).data('dynatable');
$('#filter-prop').change( function() {
var value = $(this).val();
if (value === "All")
{
pictable.queries.remove("prop");
}
else
{
pictable.queries.add("prop", value)
}
pictable.process();
});
Cheers
Related
Please check the following image:
Right now we have this array of objects defined in the schema as follows:
`destinationCountry: {
type: Array,
label: () => {return TAPi18n.__('destinationCountry.label')},
minCount:1,
maxCount:5,
},
'destinationCountry.$': {
type: Object,
label:()=>{ return " "},
},
'destinationCountry.$.country':{
type:String,
autoform:{
type: "select2",
'data-placeholder': () => {return TAPi18n.__('destinationCountry.placeholder')},
options: () => {return TAPi18n.__('countryList', {returnObjectTrees:true, joinArrays:false})},
afFieldInput: {
multiple: false,
width: 'resolve',
},
afQuickField: {
'panelClass': 'col'
},
}
},
'destinationCountry.$.days':{
type:String,
autoform:{
type:"select",
options:function (){
// here is where the suggested code would go
},
}
},`
Now, is there any way to make it so that the options in days are reactive to the ones selected in the previous array index object for $.days? We would like so that if the maximum of days in all the $.days fields is 20, as soon as you select 13 in one of them, there will only be 7 available for all the other $.days select fields. How could we achieve this reactively?
update 1:
I tried the following:
'destinationCountry.$.days': {
type: String,
autoform: {
type: "select",
options: function () {
let arr = [...Array(days.get())].map((v, i) => i);
let finalArr = [{
label: "Connection Flight",
value: 0
}]
arr.map((v, i) => {
finalArr.push({
label: (v + 1).toString(),
value: i + 1
})
});
if (this.name == 'destinationCountry.0.days') {
var usedDays = 0;
var usedArr;
if (AutoForm.getFormValues('step2') == null) {
usedArr = []
console.log("usedArr", usedArr)
} else {
usedArr = AutoForm.getFormValues('step2').insertDoc.destinationCountry
console.log("usedArr", usedArr)
}
for (let index = 0; index < usedArr.length; index++) {
const element = Number(usedArr[index].days);
usedDays += element
}
console.log("finalArr", finalArr)
if (usedDays == 0) {
console.log('usedDays', usedDays)
return finalArr
} else if (usedDays > 0) {
newFinalArr = finalArr.slice(0, "-" + (usedDays));
return newFinalArr
}
}
if (this.name == 'destinationCountry.1.days') {
var usedDays = 0;
var usedArr;
if (AutoForm.getFormValues('step2') == null) {
usedArr = []
console.log("usedArr", usedArr)
} else {
usedArr = AutoForm.getFormValues('step2').insertDoc.destinationCountry
console.log("usedArr", usedArr)
}
for (let index = 0; index < usedArr.length; index++) {
const element = Number(usedArr[index].days);
usedDays += element
}
console.log("finalArr", finalArr)
if (usedDays == 0) {
console.log('usedDays', usedDays)
return finalArr
} else if (usedDays > 0) {
newFinalArr = finalArr.slice(0, "-" + (usedDays));
return newFinalArr
}
}
},
}
},
First days option array gets erased when adding the second array object...
Now every time you select an option in the first destinationCountry.0.days, the 1.days select gets correctly reduced depending on the number you select in the 0.days field. but it re-renders and removes the option selected in the first 0.days. is there any way to set and keep the value selected for the first 0.days field?
say i have 3 items in my shopping cart, i want to remove lets say 2 of the 3 items, after the first item removed just fine, the other items will not be removed.
after some testing i saw that before the deletion of the product the data was a array, and after deleting the product, the array transformed into a object.
for example:
Basket {Klant: Object, Orderlines: Array[0]}
after adding some items: Basket {Klant: Object, Orderlines: Array[3]}
in the array are 3 objects:
{"Klant":{},"Orderlines":[
{"id":"2793","number":1,"membershiptype":"New","lineid":"3521"},
{"id":"2802","number":1,"membershiptype":"New","lineid":"3522"},
{"id":"2803","number":1,"membershiptype":"New","lineid":"3523"}
]}
and after deleting a item: Basket {Klant: Object, Orderlines: Object}
and in the object are still 2 objects:
{"Klant":{},"Orderlines":{
"0":{"id":"2793","number":1,"membershiptype":"New","lineid":"3521"}
"2":{"id":"2803","number":1,"membershiptype":"New","lineid":"3523"}
}}
i tried splicing and deleting the array, but with the same result.
i want the result to be:
{"Klant":{},"Orderlines":[
{"id":"2793","number":1,"membershiptype":"New","lineid":"3521"},
{"id":"2803","number":1,"membershiptype":"New","lineid":"3523"}
]}
here is my code:
function Basket() {
this.Klant = {};
this.Orderlines = [];
Basketdata = null;
// check if we have storage and can use it.
if (typeof (Storage) !== "undefined") {
Basketdata = sessionStorage.getItem("Basket");
total = sessionStorage.getItem("aantalitems");
$('#aantalitems').html(total);
console.log(Basketdata);
if (Basketdata != null) {
mybasket = JSON.parse(Basketdata);
this.Orderlines = mybasket.Orderlines;
this.Klant = mybasket.Klant;
}
}
if (Basketdata == null) {
Basketdata = {};
}
$('a.addcart').on('click', {this: this}, this.addArticle);
$('a.remove_cart_item').on('click', {this: this}, this.deleteArticle);
$('input.ChangeAmount').on('keyup change', {this: this}, this.changeAmount);
}
Basket.prototype = {
constructor: Basket,
deleteArticle: function (event) {
var knop = $(event.currentTarget);
var mainartikel = $(knop).parents('tr.cart_item');
var id = $(mainartikel).attr('id');
var total = 0;
for (var i in event.data.this.Orderlines) {
var line = event.data.this.Orderlines[i];
if (line.lineid === id) {
console.log(event.data.this.Orderlines[i]);
console.log(i);
event.data.this.Orderlines[i].delete = 1;
} else {
total += line.number;
}
}
sessionStorage.setItem("aantalitems", total);
$(mainartikel).fadeOut(function () {
$(this).remove();
});
sessionStorage.setItem("Basket", JSON.stringify(event.data.this));
event.data.this.sendToServer(event.data.this);
},
sendToServer: function (object) {
$.ajax({
type: "POST",
url: '/shop?addtoorder=1',
data: JSON.stringify(this),
dataType: 'json',
databasket: object
}).done(function (data) {
this.databasket.Orderlines = data.Orderlines;
for (var i in this.databasket.Orderlines) {
if (this.databasket.Orderlines[i].delete == 1) {
//here is the delete
//delete this.databasket.Orderlines[i];
this.databasket.Orderlines.splice(i,1);
}
if (this.databasket.Orderlines[i].update == 1) {
this.databasket.Orderlines[i].update = 0;
location.reload();
}
}
sessionStorage.setItem("Basket", JSON.stringify(this.databasket));
$('#totalprice').html(data.total);
if (this.databasket.Orderlines.length == 0) {
this.Orderlines = [];
$('div.content').html('\
<div style="float: left;"><b><img src="../../lib/items/Checkout/template/go-home-icon.png" width="32" height="32" alt="go-home-icon" style="vertical-align: middle;"> Back to the shop</b></div>\n\
<br><br>No items in basket\n\
');
}
});
}};
what i've tried so far:
under deleteArticle
i tried changing: event.data.this.Orderlines[i].delete = 1;
to: delete event.data.this.Orderlines[i]
which also ended up changing the array to a object
under sendToServer
i tried both
delete this.databasket.Orderlines[i];
this.databasket.Orderlines.splice(i,1);
which also ended up changing the array to a object
Try:
delete yourObj['Orderlines']
var obj = {Basket: {Klant: Object, Orderlines: Array[0]}};
obj['Orderlines'] = [
{"id":"2793","number":1,"membershiptype":"New","lineid":"3521"},
{"id":"2802","number":1,"membershiptype":"New","lineid":"3522"},
{"id":"2803","number":1,"membershiptype":"New","lineid":"3523"}
];
document.write('After adding to obj[\'Orderlines\']: ' + obj['Orderlines'] + '<br/>');
delete obj['Orderlines'];
document.write('After deleting from obj[\'Orderlines\']: ' + obj['Orderlines']);
My app has a sort- and filterable list and a few inputs and checkboxes so far.
The problem appears if the list has more than 500 items, then every every element with user input (checkboxes, input fields, menus) start to have a lag around half a second increasing with the number of items in the list. The sorting and filtering of the list is done fast enough but the lag on the input elements is too long.
The question is: how can the list and the input elements be decoupled?
Here is the list code:
var list = {}
list.controller = function(args) {
var model = args.model;
var vm = args.vm;
var vmc = args.vmc;
var appCtrl = args.appCtrl;
this.items = vm.filteredList;
this.onContextMenu = vmc.onContextMenu;
this.isSelected = function(guid) {
return utils.getState(vm.listState, guid, "isSelected");
}
this.setSelected = function(guid) {
utils.setState(vm.listState, guid, "isSelected", true);
}
this.toggleSelected = function(guid) {
utils.toggleState(vm.listState, guid, "isSelected");
}
this.selectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", true, this.items());
}.bind(this);
this.deselectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", false, this.items());
}.bind(this);
this.invertSelection = function() {
utils.toggleStateBatch(vm.listState, "GUID", "isSelected", this.items());
}.bind(this);
this.id = "201505062224";
this.contextMenuId = "201505062225";
this.initRow = function(item, idx) {
if (item.online) {
return {
id : item.guid,
filePath : (item.FilePath + item.FileName).replace(/\\/g, "\\\\"),
class : idx % 2 !== 0 ? "online odd" : "online even",
}
} else {
return {
class : idx % 2 !== 0 ? "odd" : "even"
}
}
};
// sort helper function
this.sorts = function(list) {
return {
onclick : function(e) {
var prop = e.target.getAttribute("data-sort-by")
//console.log("100")
if (prop) {
var first = list[0]
if(prop === "selection") {
list.sort(function(a, b) {
return this.isSelected(b.GUID) - this.isSelected(a.GUID)
}.bind(this));
} else {
list.sort(function(a, b) {
return a[prop] > b[prop] ? 1 : a[prop] < b[prop] ? -1 : 0
})
}
if (first === list[0])
list.reverse()
}
}.bind(this)
}
};
// text inside the table can be selected with the mouse and will be stored for
// later retrieval
this.getSelected = function() {
//console.log(utils.getSelText());
vmc.lastSelectedText(utils.getSelText());
};
};
list.view = function(ctrl) {
var contextMenuSelection = m("div", {
id : ctrl.contextMenuId,
class : "hide"
}, [
m(".menu-item.allow-hover", {
onclick : ctrl.selectAll
}, "Select all"),
m(".menu-item.allow-hover", {
onclick : ctrl.deselectAll
}, "Deselect all"),
m(".menu-item.allow-hover", {
onclick : ctrl.invertSelection
}, "Invert selection") ]);
var table = m("table", ctrl.sorts(ctrl.items()), [
m("tr", [
m("th[data-sort-by=selection]", {
oncontextmenu : ctrl.onContextMenu(ctrl.contextMenuId, "context-menu context-menu-bkg", "hide" )
}, "S"),
m("th[data-sort-by=FileName]", "Name"),
m("th[data-sort-by=FileSize]", "Size"),
m("th[data-sort-by=FilePath]", "Path"),
m("th[data-sort-by=MediumName]", "Media") ]),
ctrl.items().map(function(item, idx) {
return m("tr", ctrl.initRow(item, idx), {
key : item.GUID
},
[ m("td", [m("input[type=checkbox]", {
id : item.GUID,
checked : ctrl.isSelected(item.GUID),
onclick : function(e) {ctrl.toggleSelected(this.id);}
}) ]),
m("td", {
onmouseup: function(e) {ctrl.getSelected();}
}, item.FileName),
m("td", utils.numberWithDots(item.FileSize)),
m("td", item.FilePath),
m("td", item.MediumName) ])
}) ])
return m("div", [contextMenuSelection, table])
}
And this is how the list and all other components are initialized from the apps main view:
// the main view which assembles all components
var mainCompView = function(ctrl, args) {
// TODO do we really need him there?
// add the main controller for this page to the arguments for all
// added components
var myArgs = args;
myArgs.appCtrl = ctrl;
// create all needed components
var filterComp = m.component(filter, myArgs);
var part_filter = m(".row", [ m(".col-md-2", [ filterComp ]) ]);
var listComp = m.component(list, myArgs);
var part_list = m(".col-md-10", [ listComp ]);
var optionsComp = m.component(options, myArgs);
var part_options = m(".col-md-10", [ optionsComp ]);
var menuComp = m.component(menu, myArgs);
var part_menu = m(".menu-0", [ menuComp ]);
var outputComp = m.component(output, myArgs);
var part_output = m(".col-md-10", [ outputComp ]);
var part1 = m("[id='1']", {
class : 'optionsContainer'
}, "", [ part_options ]);
var part2 = m("[id='2']", {
class : 'menuContainer'
}, "", [ part_menu ]);
var part3 = m("[id='3']", {
class : 'commandContainer'
}, "", [ part_filter ]);
var part4 = m("[id='4']", {
class : 'outputContainer'
}, "", [ part_output ]);
var part5 = m("[id='5']", {
class : 'listContainer'
}, "", [ part_list ]);
return [ part1, part2, part3, part4, part5 ];
}
// run
m.mount(document.body, m.component({
controller : MainCompCtrl,
view : mainCompView
}, {
model : modelMain,
vm : modelMain.getVM(),
vmc : viewModelCommon
}));
I started to workaround the problem by adding m.redraw.strategy("none") and m.startComputation/endComputation to click events and this solves the problem but is this the right solution? As an example, if I use a Mithril component from a 3rd party together with my list component, how should I do this for the foreign component without changing its code?
On the other side, could my list component use something like the 'retain' flag? So the list doesn't redraw by default unless it's told to do? But also the problem with a 3rd party component would persist.
I know there are other strategies to solve this problem like pagination for the list but I would like to know what are best practices from the Mithril side.
Thanks in advance,
Stefan
Thanks to the comment from Barney I found a solution: Occlusion culling. The original example can be found here http://jsfiddle.net/7JNUy/1/ .
I adapted the code for my needs, especially there was the need to throttle the scroll events fired so the number of redraws are good enough for smooth scrolling. Look at the function obj.onScroll.
var list = {}
list.controller = function(args) {
var obj = {};
var model = args.model;
var vm = args.vm;
var vmc = args.vmc;
var appCtrl = args.appCtrl;
obj.vm = vm;
obj.items = vm.filteredList;
obj.onContextMenu = vmc.onContextMenu;
obj.isSelected = function(guid) {
return utils.getState(vm.listState, guid, "isSelected");
}
obj.setSelected = function(guid) {
utils.setState(vm.listState, guid, "isSelected", true);
}
obj.toggleSelected = function(guid) {
utils.toggleState(vm.listState, guid, "isSelected");
m.redraw.strategy("none");
}
obj.selectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", true, obj.items());
};
obj.deselectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", false, obj.items());
};
obj.invertSelection = function() {
utils.toggleStateBatch(vm.listState, "GUID", "isSelected", obj.items());
};
obj.id = "201505062224";
obj.contextMenuId = "201505062225";
obj.initRow = function(item, idx) {
if (item.online) {
return {
id : item.GUID,
filePath : (item.FilePath + item.FileName).replace(/\\/g, "\\\\"),
class : idx % 2 !== 0 ? "online odd" : "online even",
onclick: console.log(item.GUID)
}
} else {
return {
id : item.GUID,
// class : idx % 2 !== 0 ? "odd" : "even",
onclick: function(e) { obj.selectRow(e, this, item.GUID);
m.redraw.strategy("none");
e.stopPropagation();
}
}
}
};
// sort helper function
obj.sorts = function(list) {
return {
onclick : function(e) {
var prop = e.target.getAttribute("data-sort-by")
// console.log("100")
if (prop) {
var first = list[0]
if(prop === "selection") {
list.sort(function(a, b) {
return obj.isSelected(b.GUID) - obj.isSelected(a.GUID)
});
} else {
list.sort(function(a, b) {
return a[prop] > b[prop] ? 1 : a[prop] < b[prop] ? -1 : 0
})
}
if (first === list[0])
list.reverse()
} else {
e.stopPropagation();
m.redraw.strategy("none");
}
}
}
};
// text inside the table can be selected with the mouse and will be stored
// for
// later retrieval
obj.getSelected = function(e) {
// console.log("getSelected");
var sel = utils.getSelText();
if(sel.length != 0) {
vmc.lastSelectedText(utils.getSelText());
e.stopPropagation();
// console.log("1000");
}
m.redraw.strategy("none");
// console.log("1001");
};
var selectedRow, selectedId;
var eventHandlerAdded = false;
// Row callback; reset the previously selected row and select the new one
obj.selectRow = function (e, row, id) {
console.log("selectRow " + id);
unSelectRow();
selectedRow = row;
selectedId = id;
selectedRow.style.background = "#FDFF47";
if(!eventHandlerAdded) {
console.log("eventListener added");
document.addEventListener("click", keyHandler, false);
document.addEventListener("keypress", keyHandler, false);
eventHandlerAdded = true;
}
};
var unSelectRow = function () {
if (selectedRow !== undefined) {
selectedRow.removeAttribute("style");
selectedRow = undefined;
selectedId = undefined;
}
};
var keyHandler = function(e) {
var num = parseInt(utils.getKeyChar(e), 10);
if(constants.RATING_NUMS.indexOf(num) != -1) {
console.log("number typed: " + num);
// TODO replace with the real table name and the real column name
// $___{<request>res:/tables/catalogItem</request>}
model.newValue("item_update_values", selectedId, {"Rating": num});
m.redraw.strategy("diff");
m.redraw();
} else if((e.keyCode && (e.keyCode === constants.ESCAPE_KEY))
|| e.type === "click") {
console.log("eventListener removed");
document.removeEventListener("click", keyHandler, false);
document.removeEventListener("keypress", keyHandler, false);
eventHandlerAdded = false;
unSelectRow();
}
};
// window seizes for adjusting lists, tables etc
vm.state = {
pageY : 0,
pageHeight : 400
};
vm.scrollWatchUpdateStateId = null;
obj.onScroll = function() {
return function(e) {
console.log("scroll event found");
vm.state.pageY = e.target.scrollTop;
m.redraw.strategy("none");
if (!vm.scrollWatchUpdateStateId) {
vm.scrollWatchUpdateStateId = setTimeout(function() {
// update pages
m.redraw();
vm.scrollWatchUpdateStateId = null;
}, 50);
}
}
};
// clean up on unload
obj.onunload = function() {
delete vm.state;
delete vm.scrollWatchUpdateStateId;
};
return obj;
};
list.view = function(ctrl) {
var pageY = ctrl.vm.state.pageY;
var pageHeight = ctrl.vm.state.pageHeight;
var begin = pageY / 41 | 0
// Add 2 so that the top and bottom of the page are filled with
// next/prev item, not just whitespace if item not in full view
var end = begin + (pageHeight / 41 | 0 + 2)
var offset = pageY % 41
var heightCalc = ctrl.items().length * 41;
var contextMenuSelection = m("div", {
id : ctrl.contextMenuId,
class : "hide"
}, [
m(".menu-item.allow-hover", {
onclick : ctrl.selectAll
}, "Select all"),
m(".menu-item.allow-hover", {
onclick : ctrl.deselectAll
}, "Deselect all"),
m(".menu-item.allow-hover", {
onclick : ctrl.invertSelection
}, "Invert selection") ]);
var header = m("table.listHeader", ctrl.sorts(ctrl.items()), m("tr", [
m("th.select_col[data-sort-by=selection]", {
oncontextmenu : ctrl.onContextMenu(ctrl.contextMenuId, "context-menu context-menu-bkg", "hide" )
}, "S"),
m("th.name_col[data-sort-by=FileName]", "Name"),
${ <request>
# add other column headers as configured
<identifier>active:jsPreprocess</identifier>
<argument name="id">list:table01:header</argument>
</request>
} ]), contextMenuSelection);
var table = m("table", ctrl.items().slice(begin, end).map(function(item, idx) {
return m("tr", ctrl.initRow(item, idx), {
key : item.GUID
},
[ m("td.select_col", [m("input[type=checkbox]", {
id : item.GUID,
checked : ctrl.isSelected(item.GUID),
onclick : function(e) {ctrl.toggleSelected(this.id);}
}) ]),
m("td.nameT_col", {
onmouseup: function(e) {ctrl.getSelected(e);}
}, item.FileName),
${ <request>
# add other columns as configured
<identifier>active:jsPreprocess</identifier>
<argument name="id">list:table01:row</argument>
</request>
} ])
}) );
var table_container = m("div[id=l04]",
{style: {position: "relative", top: pageY + "px"}}, table);
var scrollable = m("div[id=l03]",
{style: {height: heightCalc + "px", position: "relative",
top: -offset + "px"}}, table_container);
var scrollable_container = m("div.scrollableContainer[id=l02]",
{onscroll: ctrl.onScroll()}, scrollable );
var list = m("div[id=l01]", [header, scrollable_container]);
return list;
}
Thanks for the comments!
There are some good examples of when to change redraw strategy in the docs: http://mithril.js.org/mithril.redraw.html#changing-redraw-strategy
But in general, changing redraw strategy is rarely used if the application state is stored somewhere so Mithril can access and calculate the diff without touching DOM. It seems like your data is elsewhere, so could it be that your sorts method is getting expensive to run after a certain size?
You could sort the list only after events that modifies it. Otherwise it will be sorted on every redraw Mithril does, which can be quite often.
m.start/endComputation is useful for 3rd party code, especially if it operates on DOM. If the library stores some state, you should use that for the application state as well, so there aren't any redundant and possibly mismatching data.
I'm trying to push some elements into a 2d array
The code I am using is:
samount.push ({
"value": val,
"currency": currency
});
The array that I am trying to push into samount is like this:
groupedArray =
[{value: 100, currency: 'EUR'},
{value: 100, currency: 'EUR'},
{value: 100, currencu: 'EUR'}]
In fact, the array above represents rows of one table, and at each row click, the push() function is adding do samount array a new element
After 2 row clicks, the samount array looks like:
samount =
[{value: 100, currency: 'EUR'},
{value: 100, currency: 'EUR'}]
At the third row click, the array becomes:
samount =
[{value: 200, currency: 'EUR'},
{value: 100, currency: 'EUR'},
{value: 100, currencu: 'EUR'}]
I have to mention that my code has no sum inside, so it is very strange that the push() function is summing the values of first element and still is adding them to the array
Do you know if the push() function is ok for adding element to this type of arrays?
The complete code is:
"fnDrawCallback": function (oSettings) {
var $dt = $('#dt_debts').footable({
breakpoints: { // The different screen resolution breakpoints
phone: 320,
tablet: 768
}
});
$dt.trigger('footable_resize');
var amount = new Array();
var samount = new Array();
$('#dt_debts tbody tr ').each( function () {
var iPos = oTable.fnGetPosition( this );
if (iPos!=null) {
var aData = oTable.fnGetData( iPos );
if (jQuery.inArray(aData[0], selected)!=-1) {
$(this).addClass('row_selected');
$(this).find( 'td input:checkbox' ).attr('checked', 'checked');
}
}
$(this).click( function (e) {
e.preventDefault();
var iPos = oTable.fnGetPosition( this );
var aData = oTable.fnGetData( iPos );
var iId = aData[0];
is_in_array = jQuery.inArray(iId, selected);
if (is_in_array==-1) {
selected[selected.length]=iId;
}
else {
selected = jQuery.grep(selected, function(value) {
return value != iId;
});
}
if ( $(this).hasClass('row_selected') ) {
console.info('>>> Operatie de eliminare');
$(this).removeClass('row_selected');
$(this).find( 'td input:checkbox' ).prop('checked', false);
if ($(aData[0]).data('typo') == "t-0") {
val = parseFloat(aData[9].split(" ")[0]);
currency = aData[9].split(" ")[1];
if (currency != '') {
$.each(amount, function(idx, item) {
if (item.currency == currency && item.value == val) {
amount.splice(idx, 1); // Remove current item
return false; // End the loop
}
});
}
} else if ($(aData[0]).data('typo') == "t-1") {
val = parseFloat(aData[10].split(" ")[0]);
currency = aData[10].split(" ")[1];
if (currency != '') {
$.each(samount, function(idx, item) {
if (item.currency == currency && item.value == val) {
samount.splice(idx, 1); // Remove current item
return false; // End the loop
}
});
}
}
console.info(amount);
console.info(samount);
} else {
console.info('>>> Operatie de adaugare');
$(this).addClass('row_selected');
$(this).find( 'td input:checkbox' ).attr('checked', 'checked');
if ($(aData[0]).data('typo') == "t-0") {
val = parseFloat(aData[9].split(" ")[0]);
currency = aData[9].split(" ")[1];
if (currency != '') {
amount.push ({
"value": val,
"currency": currency
});
}
} else if ($(aData[0]).data('typo') == "t-1") {
val = parseFloat(aData[10].split(" ")[0]);
currency = aData[10].split(" ")[1];
if (currency != '') {
samount.push ({
"value": val,
"currency": currency
});
}
}
console.info(amount);
console.info(samount);
}
}
Aftre spending hours of searching the error, I have figured that the error is in this part of code:
var objects1 = new Array();
objects1 = samount;
var categories1 = new Array();
var groupedObjects1 = [];
var i = 0;
_.each(objects1,function(obj){
var existingObj;
if($.inArray(obj.currency,categories1) >= 0) {
existingObj = _.find(objects1,function(o){return o.currency === obj.currency;});
existingObj.value += obj.value;
} else {
groupedObjects1[i] = obj;
categories1[i] = obj.currency;
i++;
}
});// JavaScript Document
Actually it seems that the samount array is beeing modified even if I have declared a new array for using into loop. I do not understand why the loop is modifying the initial array, even if I am not using it
Yes, push is fine. Indeed I tested it and it works as it is supposed to. What I assume is happening is the value being modified somewhere else - since you are pushing an object into the array with the value val the array would only hold a reference to the entire thing not the values it represents.
Just to illustrate this
var val = { "value" : 100 };
var storage = [ ];
storage.push(val);
console.log("value : " + val.value);
console.log("value in storage : " + storage[0]["value"]);
val.value = 200;
console.log("value : " + val.value);
console.log("value in storage : " + storage[0]["value"])
yields
value : 100
value in storage : 100
value : 200
value in storage : 200
The variables in JavaScript are pass by value for primitives (e.g., integers) but pass by reference for objects.
You can use .join() instead of .push()
//if you want the ful array as a single element:
groupedArray =
[{{value: 100, currency: 'EUR'},
{value: 100, currency: 'EUR'},
{value: 100, currencu: 'EUR'}}];
samount.join(groupedArray);
//if you want each element in groupedArray as elements in samount:
groupedArray =
[{value: 100, currency: 'EUR'},
{value: 100, currency: 'EUR'},
{value: 100, currencu: 'EUR'}];
samount.join(groupedArray);
In the following demo, after inserting "Alaska" value,
the source is updated so that the autocomplete is not showing again Alaska value.
var newSource = this.source
.slice(0,pos)
.concat(this.source.slice(pos+1));
this.source = newSource;
Anyway if I remove Alaska from the textarea, the value Alaska should be displayed again in the source.
Any hints how to restore the source data if the user delete the data from the textarea?
My idea is to access the options `source option from
$('.typeahead').on('change', function () { })
Any hints?
P.S.:
I am using jquery and underscore
You should probably rather change your matcher function in order to test over the already selected states :
var tabPresentStates = this.query.split(','),
nbPresentStates = tabPresentStates.length;
for(var iState = 0; iState < nbPresentStates; iState++) {
if(item === tabPresentStates[iState].trim())
return false;
}
See this fiddle.
Instead of changing the source you can use the sorter to exclude the values you've already selected.
http://jsfiddle.net/BwDmM/71/
P.S. I'll probably include your code in next version of Jasny's extended Bootstrap http://jasny.github.com/bootstrap :)
!function(source) {
function extractor(query) {
var result = /([^,]+)$/.exec(query);
if(result && result[1])
return result[1].trim();
return '';
}
$('.typeahead').typeahead({
source: source,
updater: function(item) {
return this.$element.val().replace(/[^,]*$/,'')+item+',';
},
matcher: function (item) {
var tquery = extractor(this.query);
if(!tquery) return false;
return ~item.toLowerCase().indexOf(tquery)
},
highlighter: function (item) {
var query = extractor(this.query).replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
return '<strong>' + match + '</strong>'
})
},
sorter: function(items) {
var beginswith = []
, caseSensitive = []
, caseInsensitive = []
, existing = $.each(this.$element.val().split(','), function(i, val) { return val.trim() })
, item
while (item = items.shift()) {
if ($.inArray(item, existing) >= 0) continue;
if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
else if (~item.indexOf(this.query)) caseSensitive.push(item)
else caseInsensitive.push(item)
}
return beginswith.concat(caseSensitive, caseInsensitive)
}
});
}(["Alabama","Alaska","Arizona","Arkansas","California","Colorado","Connecticut","Delaware","Florida","Georgia","Hawaii","Idaho","Illinois","Indiana","Iowa","Kansas","Kentucky","Louisiana","Maine","Maryland","Massachusetts","Michigan","Minnesota","Mississippi","Missouri","Montana","Nebraska","Nevada","New Hampshire","New Jersey","New Mexico","New York","North Dakota","North Carolina","Ohio","Oklahoma","Oregon","Pennsylvania","Rhode Island","South Carolina","South Dakota","Tennessee","Texas","Utah","Vermont","Virginia","Washington","West Virginia","Wisconsin","Wyoming"]);
Agreed with #SamuelCaillerie's approach to use matcher - this is just using your extractor function. So:
updater: function(item) {
return this.$element.val().replace(/[^,]*$/,'')+item+',';
},
matcher: function (item) {
var previouslySelected = (this.query.indexOf(item) != -1);
var tquery = (previouslySelected ? "" : extractor(this.query));
if(!tquery) return false;
return ~item.toLowerCase().indexOf(tquery)
},