fullcalendar with resourceDay, update list of resources - fullcalendar

I am using fullcalendar v1.5.4 with the resourceDay view (https://gist.github.com/anonymous/9c9ce0d84a6a73080177). My problem is when I am trying to update the list of resources. I can just run setCalendar() but it's quite a big and resource expensive function:
setCalendar = function (defaultView, element, currentDate) {
myCal = $(element);
myCal.fullCalendar({
minTime: '07:00:00',
maxTime: '23:59:00',
cache: true,
editable: true,
eventStartEditable: true,
eventDurationEditable: true,
...
resources: [ resourcesArray ],
dayClick: function (date, event, t, r) {
...
}
...
});
}
Running all of this code every time I want to update the columns of the resourceDay view is too expensive so I am trying to update only the list of columns (resources) and rerender them. In the function ResourceManager of the gist that I linked above (row 1257) you can see that I have tried to lift the function that populates the list of resources. By adding t._addResourceSources = _addResourceSources; and then calling on it from this function:
updateCalendar = function () {
myCal.fullCalendar('_addResourceSources', resourcesArray);
};
This is printing the new list to the console (row 1305) but I need assistance with rerendering it so the calendar actually uses the new list of resources.

Try running myCal.fullCalendar( 'rerenderEvents' );
http://fullcalendar.io/docs1/event_rendering/rerenderEvents/
It may not work though, I don't know how resources work exactly and I'm more familiar with the v2 of fullCalendar.

Related

How to structurally compare the previous and new value of nested objects that are being used in `watch`, in Options API?

I have a question which is a mix of both composition API and options API
What I want to do: I want to watch an object. That object is deeply nested with all kinds of data types.
Whenever any of the nested properties inside change, I want the watch to be triggered.
(This can be done using the deep: true option).
AND I want to be able to see the previous value and current value of the object.
(this doesn't seem to be possible because Vue stores the references of the objects, so, now the value and prevValue point to the same thing.)
In Vue3 docs, for the watch API, it says this
However, watching a reactive object or array will always return a reference to the
current value of that object for both the current and previous value of the state.
To fully watch deeply nested objects and arrays, a deep copy of values may be required.
This can be achieved with a utility such as lodash.cloneDeep
And this following example is given
import _ from 'lodash'
const state = reactive({
id: 1,
attributes: {
name: ''
}
})
watch(
() => _.cloneDeep(state),
(state, prevState) => {
console.log(state.attributes.name, prevState.attributes.name)
}
)
state.attributes.name = 'Alex' // Logs: "Alex" ""
Link to docs here - https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watching-reactive-objects
However, this is composition API (if I'm not wrong).
How do I use this way of using cloneDeep in a watch defined in options API?
As an example, this is my code
watch: {
items: {
handler(value, prevValue) {
// check if value and prevValue are STRUCTURALLY EQUAL
let isEqual = this.checkIfStructurallyEqual(value, prevValue)
if (isEqual) return
else this.doSomething()
},
deep: true,
},
}
I'm using Vue 3 with Options API.
How would I go about doing this in Options API?
Any help would be appreciated! If there's another way of doing this then please do let me know!
I also asked this question on the Vue forums and it was answered.
We can use the same syntax as provided in the docs in Options API using this.$watch()
data() {
id: 1,
attributes: {
name: ''
}
}
this.$watch(
() => _.cloneDeep(this.attributes),
(state, prevState) => {
console.log(state.name, prevState.name)
}
)
this.attributes.name = 'Alex' // Logs: "Alex" ""

How do I return an entire paged set from the Jira API using Ramda?

I'm using the Nodejs library for talking to Jira called jira-connector. I can get all of the boards on my jira instance by calling
jira.board.getAllBoards({ type: "scrum"})
.then(boards => { ...not important stuff... }
the return set looks something like the following:
{
maxResults: 50,
startAt: 0,
isLast: false,
values:
[ { id: ... } ]
}
then while isLast === false I keep calling like so:
jira.board.getAllBoards({ type: "scrum", startAt: XXX })
until isLast is true. then I can organize all of my returns from promises and be done with it.
I'm trying to reason out how I can get all of the data on pages with Ramda, I have a feeling it's possible I just can't seem to sort out the how of it.
Any help? Is this possible using Ramda?
Here's my Rx attempt to make this better:
const pagedCalls = new Subject();
pagedCalls.subscribe(value => {
jira.board.getAllBoards({ type:"scrum", startAt: value })
.then(boards => {
console.log('calling: ' + value);
allBoards.push(boards.values);
if (boards.isLast) {
pagedCalls.complete()
} else {
pagedCalls.next(boards.startAt + 50);
}
});
})
pagedCalls.next(0);
Seems pretty terrible. Here's the simplest solution I have so far with a do/while loop:
let returnResult = [];
let result;
let startAt = -50;
do {
result = await jira.board.getAllBoards( { type: "scrum", startAt: startAt += 50 })
returnResult.push(result.values); // there's an array of results under the values prop.
} while (!result.isLast)
Many of the interactions with Jira use this model and I am trying to avoid writing this kind of loop every time I make a call.
I had to do something similar today, calling the Gitlab API repeatedly until I had retrieved the entire folder/file structure of the project. I did it with a recursive call inside a .then, and it seems to work all right. I have not tried to convert the code to handle your case.
Here's what I wrote, if it will help:
const getAll = (project, perPage = 10, page = 1, res = []) =>
fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/repository/tree?recursive=true&per_page=${perPage}&page=${page}`)
.then(resp => resp.json())
.then(xs => xs.length < perPage
? res.concat(xs)
: getAll(project, perPage, page + 1, res.concat(xs))
)
getAll('gitlab-examples/nodejs')
.then(console.log)
.catch(console.warn)
The technique is pretty simple: Our function accepts whatever parameters are necessary to be able to fetch a particular page and an additional one to hold the results, defaulting it to an empty array. We make the asynchronous call to fetch the page, and in the then, we use the result to see if we need to make another call. If we do, we call the function again, passing in the other parameters needed, the incremented page number, and the merge of the current results and the ones just received. If we don't need to make another call, then we just return that merged list.
Here, the repository contains 21 files and folders. Calling for ten at a time, we make three fetches and when the third one is complete, we resolve our returned Promise with that list of 21 items.
This recursive method definitely feels more functional than your versions above. There is no assignment except for the parameter defaulting, and nothing is mutated along the way.
I think it should be relatively easy to adapt this to your needs.
Here is a way to get all the boards using rubico:
import { pipe, fork, switchCase, get } from 'rubico'
const getAllBoards = boards => pipe([
fork({
type: () => 'scrum',
startAt: get('startAt'),
}),
jira.board.getAllBoards,
switchCase([
get('isLast'),
response => boards.concat(response.values),
response => getAllBoards(boards.concat(response.values))({
startAt: response.startAt + response.values.length,
})
]),
])
getAllBoards([])({ startAt: 0 }) // => [...boards]
getAllBoards will recursively get more boards and append to boards until isLast is true, then it will return the aggregated boards.

Best way to perform Update call when input field value is changed in Meteor JS?

Hypothetical example - you have an "Items" collection, where each item has a quantity and price that's stored in the db.
That quantity is an input field.
We want the database to be updated when the quantity is changed - with no "submit" button. There are multiple ways of going about this. Two examples:
Update db on "changed":
'change input.qty': function (evt) {
var qty = $(evt.target).val();
if (qty==null){
qty=0;
};
Items.update(this._id,{$set:{quantity: Number(qty)}});
},
Update db on "keyup":
'keyup input.qty': function (evt) {
var qty = $(evt.target).val();
if (qty==null){
qty=0;
};
Items.update(this._id,{$set:{quantity: Number(qty)}});
},
1 is more efficient - it only performs the update call once, after the user has clicked outside of the input box. However, it's a worse user experience, because the updates are not reflected on the page as they're typing. (For example, say the "price" field is calculated reactively based on your input quantity)
2 is a better user experience but can be extremely inefficient(ie typing in 103.58 makes FIVE database calls)
Are there better alternatives or a good middle ground?
That's the exact situation for which _.throttle method was created.
'keyup input.qty': _.throttle(function (evt) {
...
}, 350),
When you wrap your handler with _.throttle that way, it will be called only once per the given number of milliseconds, even if the input keep changing more frequently.
350 is a good value in most cases, though the exact optimum value may depend on the interface you're designing.
Plagiarising #Hubert OG's answer, except to recommend the "debounce" function instead, and the "input" & "change" events. (_.debounce works the same as the accepted solution from #Dave without the boilerplate.)
That's the exact situation for which _.debounce method was created.
'input input.qty, change input.qty': _.debounce(function (evt) {
...
}, 350),
When you wrap your handler with _.debounce that way, it will be called only once after all input has stopped (for at least a given number of msec)
Here's how I usually attack this problem:
var handle = null;
------------------------
'input input.qty': function (evt) {
var self = this;
if (handle)
clearTimeout(handle);
handle = setTimeout(function () {
var qty = $(evt.target).val();
if (qty==null){
qty=0;
};
Items.update(self._id,{$set:{quantity: Number(qty)}});
}, 500);
},
You can play with the number 500 a bit to get it to your liking. With this solution you'll only get a database call when the user has stopped typing for 500 milliseconds.
I'd switch to the input event as well, it takes care of cut, paste, and key entries.
Both seem like good solutions. The first is better in my opinion - keyup also registers things like pressing enter or other keys. Since I have multiple items with multiple quantity fields, here's what I ended up going with:
var handle = [];
'input input.qty': function (evt) {
var id = this._id;
if (handle[id]){
clearTimeout(handle[id]);
}
handle[id] = setTimeout(function () {
var qty = $(evt.target).val();
Items.update(id,{$set:{quantity: Number(qty)}});
}, 750);
}

Meteor re-activity issue inside template helper

I currently have a template where I am querying the database with the following query.
allMessages = Messages.find({$or: [{type: "user_message"}, {type: "system_message", time: {$gt: (Date.now() - 180000)} }]}, {sort: {time: 1 }}).fetch()
Now obviously the template helper gets re-run whenever something new goes into or is removed from this set of data, which is exactly what I want. The issue arises when a system_message gets older than 2 minutes and I no longer want that message to by apart of my query. The data does not update when this happens, and only updates when a new message comes in or a for some reason a message is removed.
Does anyone know why this might be the case? It seems to me that there shouldn't be an issue as the data on the query is changing so it should be re-running but it isn't.
It isn't working because Date.now() isn't a reactive variable. If you were to set the date limit in something like a session variable or a ReactiveDict, it would cause your helper to recompute. Here's an example using the Session:
Template.myTemplate.allMessages = function() {
var oldestMessageDate = Session.get('oldestMessageDate');
var selector = {
$or: [
{type: "user_message"},
{type: "system_message", time: {$gt: oldestMessageDate}}
]
};
return Messages.find(selector, {sort: {time: 1}});
};
Template.myTemplate.created = function() {
this.intervalId = Meteor.setInterval(function() {
Session.set('oldestMessageDate', new Date - 120000);
}, 1000);
};
Template.myTemplate.destroyed = function() {
Meteor.clearInterval(this.intervalId);
};
Every second after the template is created, it changes oldestMessageDate to a new date which is two minutes in the past. Note that the intervalId is stored in the template instance and later cleaned up in the destroyed callback so it won't keep running after the template is no longer in use. Because oldestMessageDate is a reactive variable, it should cause your allMessages helper to continually rerun.

How to 'transform' data returned via a Meteor.publish?

Meteor Collections have a transform ability that allows behavior to be attached to the objects returned from mongo.
We want to have autopublish turned off so the client does not have access to the database collections, but we still want the transform functionality.
We are sending data to the client with a more explicit Meteor.publish/Meteor.subscribe or the RPC mechanism ( Meteor.call()/Meteor.methods() )
How can we have the Meteor client automatically apply a transform like it will when retrieving data directly with the Meteor.Collection methods?
While you can't directly use transforms, there is a way to transform the result of a database query before publishing it. This is what the "publish the current size of a collection" example describes here.
It took me a while to figure out a really simple application of that, so maybe my code will help you, too:
Meteor.publish("publicationsWithHTML", function (data) {
var self = this;
Publications
.find()
.forEach(function(entry) {
addSomeHTML(entry); // this function changes the content of entry
self.added("publications", entry._id, entry);
});
self.ready();
});
On the client you subscribe to this:
Meteor.subscribe("publicationsWithHTML");
But your model still need to create a collection (on both sides) that is called 'publications':
Publications = new Meteor.Collection('publications');
Mind you, this is not a very good example, as it doesn't maintain the reactivity. But I found the count example a bit confusing at first, so maybe you'll find it helpful.
(Meteor 0.7.0.1) - meteor does allow behavior to be attached to the objects returned via the pub/sub.
This is from a pull request I submitted to the meteor project.
Todos = new Meteor.Collection('todos', {
// transform allows behavior to be attached to the objects returned via the pub/sub communication.
transform : function(todo) {
todo.update = function(change) {
Meteor.call('Todos_update', this._id, change);
},
todo.remove = function() {
Meteor.call('Todos_remove', this._id);
}
return todo;
}
});
todosHandle = Meteor.subscribe('todos');
Any objects returned via the 'todos' topic will have the update() and the remove() function - which is exactly what I want: I now attach behavior to the returned data.
Try:
let transformTodo = (fields) => {
fields._pubType = 'todos';
return fields;
};
Meteor.publish('todos', function() {
let subHandle = Todos
.find()
.observeChanges({
added: (id, fields) => {
fields = transformTodo(fields);
this.added('todos', id, fields);
},
changed: (id, fields) => {
fields = transformTodo(fields);
this.changed('todos', id, fields);
},
removed: (id) => {
this.removed('todos', id);
}
});
this.ready();
this.onStop(() => {
subHandle.stop();
});
});
Currently, you can't apply transforms on the server to published collections. See this question for more details. That leaves you with either transforming the data on the client, or using a meteor method. In a method, you can have the server do whatever you want to the data.
In one of my projects, we perform our most expensive query (it joins several collections, denormalizes the documents, and trims unnecessary fields) via a method call. It isn't reactive, but it greatly simplifies our code because all of the transformation happens on the server.
To extend #Christian Fritz answer, with Reactive Solution using peerlibrary:reactive-publish
Meteor.publish("todos", function() {
const self = this;
return this.autorun(function(computation) {
// Loop over each document in collection
todo.find().forEach(function(entry) {
// Add function to transform / modify each document here
self.added("todos", entry._id, entry);
});
});
});

Resources