Modify dynamically added content - dynamic

I have used the search.. and tried many things.. but I just dont get it to work. Here is what I do:
On change of a select, I request some Data, in JSON-Format.
After that, I call a function:
if (result.events) {
$('#filter_result').trigger('put_result', result);
}
In put_results() I build the HTML:
$('#filter_result').bind('put_result', function (e, data) {
html = '<h1 class="header_dates">Termine</h1>';
// and some more
$(this).html(html).stop(true, true).stop(true, true).slideDown();
}
What I want to do now, is modify part of the #filter_result HTML. I want to look in one that was build, and get the value.
The HTML markup that is inserted looks like this:
<table>
<thead>
<tr>
<th>Großraum</th>
<th>Thema</th>
<th>Termin</th>
<th>Anmeldung</th>
</tr>
</thead>
<tbody>
<tr class="last">
<td class="key_0">Berlin</td>
<td class="key_1">273</td>
<td class="key_2">27.04.2012</td>
<td class="key_4">Jetzt anmelden</td>
</tr>
</tbody>
</table>
To do that, I have written a function that gets called just after the "trigger" line. I thought about something like:
function unsetlink(){
if(Date.parse($('.key_2').live().HTML()).equals(Date.today().add(2).days())) {
alert('false');
}
else {
alert('true');
}
}
I use date.js for date comparison. The functions logic is working, thats not the problem. Its just that I think i'm using live at the wrong place or something like that. I just cant find an answer.. Anything that could help is welcome.
Thank you.

Given your updates, and making the assumption that the content exists when you call it, the function should be:
function unsetlink() {
if (Date.parse($('.key_2').text()).equals(Date.today().add(2).days())) {
alert('false');
} else {
alert('true');
}
}
Here is a hacked together test for the function: http://jsfiddle.net/MarkSchultheiss/86mDa/
EDIT2: Here is an example of processing multiple injected content groups: http://jsfiddle.net/MarkSchultheiss/86mDa/3/
Here is the code in that example:
function unsetlink(daysadvance) {
$('.key_2').each(function() {
if (Date.parse($(this).text()).equals(Date.today().add(daysadvance).days())) {
$(this).addClass('showFalse');
$('#me').text($(this).text());//this is just to show what we are processing currnetly
alert('false');
} else {
$(this).addClass('showTrue');
alert('true');
}
})
}
$('#me').after('<table><thead><tr><th>Großraum</th><th>Thema</th><th>Termin</th><th>Anmeldung</th></tr></thead><tbody><tr class="last"><td class="key_0">Berlin</td><td class="key_1">273</td><td class="key_2">27.04.2012</td><td class="key_4">register</td></tr><tr class="last"><td class="key_0">Berlin</td><td class="key_1">273</td><td class="key_2">28.04.2012</td><td class="key_4">register</td></tr></tbody></table>');
var daysadvance = 3;
unsetlink(daysadvance);

I think you want to access some elements that will be added to the DOM inside the custom event put_result.
If that is the case, then I suggest you write a callback function to the put_result event which will be called after you append to DOM. See below,
$('#filter_result').bind('put_result', function (e, data, callback) {
html = '<h1 class="header_dates">Termine</h1>';
// and some more
$(this).html(html).stop(true, true).slideDown(); //removed one .stop(true, true)
callback(); //invoke the callback at the end or when needed accordingly
});
And in the trigger,
//unsetlink is your callback function - Change it to an inner function
//if you want to do more and then call unsetlink at the end.
$('#filter_result').trigger('put_result', [result, unsetlink]);
And change your unsetlink function as
function unsetlink(){
if(Date.parse($('.key_2').text()).equals(Date.today().add(2).days())) {
alert('false');
} else {
alert('true');
}
}

Related

Svelte {#if variable} block does not react to variable updates within the block

I would like to populate a table with visible rows in Svelte.
My current attempt relies on a {#if variable} test, where the rendered row updates the variable. Unfortunately, the test does not appear to react to changes to the variable. Perhaps this is as designed but the documentation does not appear to address this. Essentially:
<table>
<tbody>
{#each rows as row}
{#if renderIt==true}
<tr use:updateRenderIt>
<td>cell</td>
</tr>
{/if}
{/each}
</tbody>
</table>
I think my understanding of the timing is lacking :(. Perhaps the {#if} block cannot react to each renderIt change. There are quite a few examples of {#if} blocks, but none appear to rely on a variable which is changed within the block.
There is a running example in the Svelte playground. The console divider can be moved vertically to change the viewport dimensions.
If someone knows of a way to achieve this it would be appreciated! I can do it in traditional Javascript, but my Svelte expertise is limited :).
What I'm assuming you want is to have a state on each row when it is visible.
To do so you will need to store some data with your row, so instead of your row being a list of numbers and a single boolean to say if all rows are visible or not, it will be a list of objets that have a property visible:
let rows = [];
for (let i = 0; i < 100; i++) {
rows.push({
index: i,
visible: false,
});
};
Next, to capture when visibility changes on those rows, use Intersection Observer API:
let observer = new IntersectionObserver(
(entries, observer) => {
console.log(entries);
}
);
And use a svelte action to add that observer to elements:
<script>
...
let intersect = (element) => {
observer.observe(element);
};
</script>
<table>
<tbody>
{#each rows as row (row.index)}
<tr
use:intersect>
<td>{row.visible}</td>
</tr>
{/each}
</tbody>
</table>
To pass the intersecting state back to the element throw a custom event on it:
let observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
entry.target.dispatchEvent(
new CustomEvent("intersect", { detail: entry.isIntersecting })
);
});
}
);
And finally capture that event and modify the state:
<tr use:intersect
on:intersect={(event) => (row.visible = event.detail)} >
<td>{row.visible}</td>
</tr>
To render rows up to how many can fit on screen you could make the defaut state visible: true, and then wrap the element with an {#if row.visible}<tr .... </tr>{/if}. After the first event you would then remove the observer from the element using observer.unobserve by either updating the svelte action or in the observer hook.

Update Datatables column searching dropdownbox after ajax load

I added a filtering function with Individual column searching (https://datatables.net/examples/api/multi_filter_select.html) to my datatable which is working fine.
This table also has a button to reload table data. The button triggers code like:
table.ajax.url("foo").load();
It updates table data correctly. Now, I want to update searching dropdown box with new column data. I want to empty dropdown box something like select.empty() then fill the box, but not sure how. I think this update process should be written in "rowCallback".
Summary
To rebuild the drop-downs after each ajax call, here is one approach:
Instead of using the DataTables ajax option, you can fetch the data using a jQuery ajax call, outside of DataTables.
Use the jQuery done function populate the table, and re-build the drop-downs after each ajax call.
This approach ensures that the ajax data has been fetched before any additional processing takes place.
Walkthrough
Assume we have a button like this:
<button type="button" onclick="fetchData();">Reload Data</button>
And a HTML table like this:
<table id="example" class="display" style="width:100%">
<tfoot>
<tr>
<th></th>
<th></th>
<th></th> <!-- you may need more footer cells in your table -->
</tr>
</tfoot>
</table>
Here is the related fetchData function, which clears all existing data, then re-populates the table with the newly fetched data:
function fetchData() {
$.ajax({
url: [your url goes here], // your URL here
context: document.body
}).done(function( data ) {
var table = $('#example').DataTable();
table.clear();
table.rows.add(data);
buildSelectLists();
table.draw();
});
}
The function to rebuild the select lists is identical to the logic from the DataTables example solution:
function buildSelectLists() {
$('#example').DataTable().columns().every(function() {
var column = this;
var select = $('<select><option value=""></option></select>')
.appendTo($(column.footer()).empty())
.on('change', function() {
var val = $.fn.dataTable.util.escapeRegex(
$(this).val()
);
column
.search(val ? '^' + val + '$' : '', true, false)
.draw();
});
column.data().unique().sort().each(function(d, j) {
select.append('<option value="' + d + '">' + d + '</option>')
});
});
}
Finally, the DataTable is defined in a "document ready" function:
$(document).ready(function() {
$('#example').DataTable({
// your options here - but no need for the ajax or data options
"initComplete": function() {
fetchData(); // make sure the table contains data, when it is created
}
});
});
Alternatively:
You can achieve a similar result by using the DataTables ajax option which makes use of a function:
Example (taken from the documentation here):
$('#example').dataTable( {
"ajax": function (data, callback, settings) {
callback(
JSON.parse( localStorage.getItem('dataTablesData') )
);
}
} );
I think in this case, it is a bit cleaner to keep the ajax call in its own separate function.

Datatables doesn't populate when I do row.add().draw()

I have an app that I'm trying to migrate to using datatables. I'm stripping out as much of the underlying irrelevant code as I can here. Basically the app is one where a user makes a series of selections from dropdowns and based on those dropdowns it picks a json file, clears the table, runs through the elements in the json object, and adds rows to my table. At the end of all that it redraws.
To start off though I populate it with default data using th.e same operation. Either scenario produces the same result, but it somewhat explains why the initialization is in a separate IIFE
When I do that, I wind up with nothing. No js errors, no nothing. Just an empty datatable. Here's my relevant html code (I am simplifying it and obfuscating things, but nothing that should be relevant:
<table id="conf">
<thead>
<tr>
<th class="header">a</th>
<th class="header">b</th>
<th class="header">c</th>
<th class="header">d</th>
<th class="header">e</th>
<th class="header">f</th>
<th class="header">g</th>
</tr>
</thead>
</table>
And the javascript
(function() {
'use strict';
var date="20190101"
$(document).ready(function(){
var table = $('#conf').DataTable({
ordering:true,
paging:false
});
ConfTable.PopulateTable(date);
});
}());
var ConfTable = (function() {
'use strict';
var table = $('#conf').DataTable();
return{
PopulateTable:function(){
$.getJSON("data/conf/"+date+".json?_=" + new Date().getTime(),
function(data, tableParam){
table.clear().draw();
GenerateTable(data)
table.draw();
});
}
}
function GenerateTable(data) {
for(var i=0; i<data.dockets.length; i++){
addRow(data.dockets[i]);
}
}
function addRow(thisCase) {
table.row.add(
[
1,2,3,4,5,6,7
]
);
}
}());
I do intend to put actual data in the array eventually, but just to simplify for troubleshooting that is what I'm trying. I have also tried adding it as an array of arrays using table.rows.add, and that array would look like this:
[
[1,2,3,4,5,6,7],
[1,2,3,4,5,6,7],
[1,2,3,4,5,6,7],
...
]
I have also attempted to put an object around the wrapper:
{"data": [1,2,3,4,5,6,7]}
In all of these cases, the end result is an initialized datatable with the options I selected with no data in it, as if I had not performed the table.row.add operation. ("No data available in table") I've stepped through and confirmed that the code is being executed as intended, but I am stuck as to why I'm not getting any results back. If you need any additional information I'm happy to provide it. Thanks.
PROBLEM
The problem with your code is in the order of code execution.
Your table gets initialized in the ConfTable before it gets initialized in the ready event handler.
You're using function expressions ((function(){}())) which are executed in the order they are defined ( and before the document is ready). Therefore table fails to initialize in ConfTable and your row.add() method fails.
SOLUTION
Either retrieve table instance inside each function or pass table variable as an argument.
(function() {
'use strict';
$(document).ready(function(){
var table = $('#example').DataTable({
ordering:true,
paging:false
});
ConfTable.PopulateTable();
});
}());
var ConfTable = (function() {
'use strict';
return{
PopulateTable:function(){
var table = $('#example').DataTable();
table.clear().draw();
GenerateTable({
dockets: [[1,2,3,4,5,6]]
});
table.draw();
}
}
function GenerateTable(data) {
for(var i=0; i<data.dockets.length; i++){
addRow(data.dockets[i]);
}
}
function addRow(thisCase) {
var table = $('#example').DataTable();
table.row.add(thisCase);
}
}());
EXAMPLE
See this example for code and demonstration.

Changing complex computed object in vue.js

I have complicated object for a table. Looks like this:
{
1510002000: {
date: "07.11.17"
hours: {
1510002000:{
activity:{
activity: "Тест",
color: "#00ff00",
end_at: 1510005600,
start_at: 1510002000,
type_id: 1
}
},
1510005600: {
...
},
...
}
},
....
}
This is a code from template that uses this object:
<tr v-for="date in this.tds">
<td>{{ date.date }}</td>
<td is="hour-td"
v-for="hour in date.hours"
:color="hour.activity.color"
:start_at="hour.activity.start_at"
:end_at="hour.activity.end_at"
:activity="hour.activity.activity"
:type_id="hour.activity.type_id"
>
</td>
</tr>
I evaluated it as a computed property, but I need to rerender table when parent component provides data assync, so I have a watcher for prop (prop called "activities"):
watch: {
activities: function(){
var vm = this;
let dth = new DateTimeHelper;
if (this.activities.length > 0){
this.activities.forEach(function(activity){
let dateTimestamp = dth.getDateTimestampFromTimestamp(activity.start_at); // just getting the key
if (vm.tds[dateTimestamp]){
if (vm.tds[dateTimestamp].hours[activity.start_at]){
vm.tds[dateTimestamp].hours[activity.start_at].activity.activity = activity.activity;
vm.tds[dateTimestamp].hours[activity.start_at].activity.color = activity.color;
vm.tds[dateTimestamp].hours[activity.start_at].activity.type_id = activity.type_id;
}
}
});
}
console.log(vm.tds) // here I can see that the object is filled with new data
}
},
The problem is that the table doesn't rerender. More precisely, the component "hour-td" does not contain the new data.
Also I've tried to use Vue.set, but no success with that
Can you help me with the updating table? I've spent like 5 hours for refactoring and attempts.
Thanks in advance
SOLUTION
In my case there can be two states: there are activities, there are no activities. So I made two computed props for each case and render them separately and switch by v-if="activities.length"
I think that your problem is with Vue known issue for Change Detection Caveats (you can read here) with array direct assignation, that don't detect changes.
You should change this part of code (with direct array assignation):
if (vm.tds[dateTimestamp]){
if (vm.tds[dateTimestamp].hours[activity.start_at]){
vm.tds[dateTimestamp].hours[activity.start_at].activity.activity = activity.activity;
vm.tds[dateTimestamp].hours[activity.start_at].activity.color = activity.color;
vm.tds[dateTimestamp].hours[activity.start_at].activity.type_id = activity.type_id;
}
}
With the Vue.set() option for arrays in order to detect the change and re-renders the component. It worked for me in differents occassions:
// Vue.set(object, key, value)
// something like this:
Vue.set(vm.tds[dateTimestamp].hours[activity.start_at].activity.activity,1,activity.activity);
More info here: https://codingexplained.com/coding/front-end/vue-js/array-change-detection
Edit:
I see now that you said:
Also I've tried to use Vue.set, but no success with that
What you mean with: "no success" ? Can you share the code? I have the same issue and I resolved with Vue.set..
You can also take a look to vm.$forceUpdate(), try to execute after the last console.log or grab all the code inside a vm.$nextTick( [callback] ) in order to execute all the actions (load data in the table) and then, re-render the component on next tick.
More info here: https://v2.vuejs.org/v2/api/#vm-forceUpdate && https://v2.vuejs.org/v2/api/#Vue-nextTick
Edit 2:
I think that your problem is with the index of the array, you should take a look here: https://v2.vuejs.org/v2/guide/list.html#Array-Change-Detection .
Try changing the:
if (vm.tds[dateTimestamp]){
if (vm.tds[dateTimestamp].hours[activity.start_at]){
vm.tds[dateTimestamp].hours[activity.start_at].activity.activity = activity.activity;
vm.tds[dateTimestamp].hours[activity.start_at].activity.color = activity.color;
vm.tds[dateTimestamp].hours[activity.start_at].activity.type_id = activity.type_id;
}
}
and simplify with:
if (vm.tds[dateTimestamp] && vm.tds[dateTimestamp].hours[activity.start_at]){
Vue.set( vm.tds, vm.tds.indexOf(vm.tds[dateTimestamp].hours[activity.start_at]), activity);
}
Hope it helps!

Meteor with DataTables: Meteor._atFlush TypeError

I'm trying to use DataTables (via mrt add datatables) with Meteor. I've variously tried adding the $('#mytableid').dataTable() to the Meteor.subscribe callback, Meteor.autorun, Meteor.startup, and Template.mytemplate.rendered -- all resulting in the following exception and a No data available in table message.
Any pointers?
Exception from Meteor._atFlush: TypeError: Cannot call method 'insertBefore' of null
at http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:405:27
at LiveRange.operate (http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:459:11)
at LiveRange.replaceContents (http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:403:17)
at http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:996:37
at withEventGuard (http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:105:16)
at http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:981:9
at http://localhost:3000/packages/deps/deps-utils.js?f3fceedcb1921afe2b17e4dbd9d4c007f409eebb:106:13
at http://localhost:3000/packages/deps/deps.js?1df0a05d3ec8fd21f591cfc485e7b03d2e2b6a01:71:15
at Array.forEach (native)
at Function._.each._.forEach (http://localhost:3000/packages/underscore/underscore.js?47479149fe12fc56685a9de90c5a9903390cb451:79:11)
Update: Potentially related to this issue, for which the best solution found was to call dataTable() for each row -- not ideal in that case, and potentially catastrophic in mine given the very large number of rows.
Dror's answer is the right start for sure. Here's the best practice the way I see it currently:
HTML
<template name="data_table">
{{#constant}}
<table class="table table-striped" id="tblData">
</table>
{{/constant}}
</template>
JS:
Template.data_table.created = function() {
var _watch = Collection.find({});
var handle = _watch.observe({
addedAt: function (doc, atIndex, beforeId) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnAddData(doc);
}
, changedAt: function(newDoc, oldDoc, atIndex) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnUpdate(newDoc, atIndex);
}
, removedAt: function(oldDoc, atIndex) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnRemove(atIndex);
}
});
};
Template.data_table.rendered = function () {
if (!($("#tblData").hasClass("dataTable"))) {
$('#tblStudents').dataTable({
"aaSorting": []
, "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
, "sPaginationType": "bootstrap"
, "oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
, "aoColumns": [
{ "sTitle": "First Data", "mData": function (data) { return data.firstData },
]
});
$('#tblData').dataTable().fnClearTable();
$('#tblData').dataTable().fnAddData(Collection.find().fetch());
}
};
Since Meteor is reactive, you need to create an empty table first and then add the rows.
Create the table:
$('#myTable').dataTable( {
"aoColumns": cols, //the column titles
"sDom": gridDom
} );
Add rows should look something like:
var myCursor = myCollection.find({});
var handle = myCursor.observe({
added: function (row) {
//Add in here the data to the table
},
});
The answered approach still stands but requires some changes after two years.
There is no such thing as constant anymore.
Instead of an empty table, put the thead but NOT the tbody. This way, columns are defined properly.
Put the observer in the onRendered (not rendered anymore) and NOT on onCreated (not created anymore). Otherwise, table is only filled at first creation and not when you come back.
clear and fill methods on the onRendered are not required.
In the observe method, make sure that you are adding an array and not an object. Otherwise fnAdd cannot render content
fnRemove is no more. Use fnDeleteRow instead
I'm not sure if the "is visible check" is required or not. There does not seem to be a problem when you remove it too, so I removed it.
With the above remarks, here is the code:
Template.creamDealsList.onRendered(function () {
if (!($("#tblData").hasClass("dataTable"))) {
$('#tblStudents').dataTable({
// Those configuration are not really important, use it as they are convenient to you
"aaSorting": []
, "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
, "sPaginationType": "bootstrap"
, "oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
, "aoColumns": [
{ "sTitle": "First Data", "mData": function (data) { return data.firstData },
]
});
}
var _watch = Collection.find({});
var convertDocToArray = function (doc) {return [doc._id, doc.name]}
var handle = _watch.observe({
addedAt: function (doc, atIndex, beforeId) {
$('#tblData').dataTable().fnAddData(convertDocToArray(doc));
}
, changedAt: function(newDoc, oldDoc, atIndex) {
$('#tblData').dataTable().fnUpdate(convertDocToArray(newDoc), atIndex);
}
, removedAt: function(oldDoc, atIndex) {
$('#tblData').dataTable().fnDeleteRow(atIndex);
}
});
})
And simple HTML would be like:
<template name="data_table">
<table class="table table-striped" id="tblData">
<thead>
<th>Id</th>
<th>Name</th>
</thead>
</table>
</template>
This worked at least for me. I'm not seeing flush errors anymore. And also, when you render later, sometimes, there was a no data available message in the datatable and that disappeared as well.
Here's my implementation of dataTables in Meteor, using CoffeeScript and HTML. It works, and it's simpler, but may be less efficient than the existing answers for large tables (it's quick in my case though).
Template Function
Template.dashboard.rendered = ->
# Tear down and rebuild datatable
$('table:first').dataTable
"sPaginationType": "full_numbers"
"bDestroy": true
HTML
<template name="dashboard">
<table class="table table-bordered" id="datatable_3" style="min-width:1020px">
<thead>
<!-- code removed to save space -->
</thead>
<tbody id="previous-jobs-list">
{{#each jobs}}
{{> jobRow}}
{{/each}}
</tbody>
</table>
</template>
<template name="jobRow">
<tr>
<td>{{number}}</td>
<td>{{normalTime date}}</td>
<!-- more code removed to save space -->
</tr>
</template>
Where dashboard.jobs is a simple .find(...) on the collection.