Svelte access a store variable in child component script tag - store

I have a Svelte & Sapper app where I am using a Svelte writable store to set up a variable with an initial blank string value:
import { writable } from 'svelte/store';
export let dbLeaveYear = writable('');
In my Index.svelte file I am importing this and then working out the value of this variable and setting it (I am doing this within the onMount function of ```Index.svelte if this is relevant):
<script>
import {dbLeaveYear} from "../stores/store.js"
function getCurrentLeaveYear() {
const today = new Date();
const currYear = today.getFullYear();
const twoDigitYear = currYear.toString().slice(-2);
const cutoffDate = `${twoDigitYear}-04-01`
const result = compareAsc(today, new Date(cutoffDate));
if (result === -1) {
$dbLeaveYear = `${parseInt(twoDigitYear, 10) - 1}${twoDigitYear}`;
} else {
$dbLeaveYear = `${twoDigitYear}${parseInt(twoDigitYear, 10) + 1}`;
}
}
onMount(() => {
getCurrentLeaveYear();
});
</script>
I have a child component being rendered in the Index.svelte
<Calendar />
Inside the Calendar child component I am importing the variable and trying to access it to perform a transform on it but I am getting errors that it is still blank - it is seemingly not picking up the assignment from Index.svelte:
<script>
import {dbLeaveYear} from "../stores/store.js"
const calStart = $dbLeaveYear.slice(0, 2)
</script>
However if I use the value in an HTML element in the same Calendar child component with <p>{$dbLeaveYear}</p> it is populated with the value from the calculation in Index.svelte.
How can I access the store variable inside the <script> tag of the child component? Is this even possible? I've tried assiging in onMount, I've tried assigning in a function - nothing seems to work and it always says that $dbLeaveYear is a blank string.
I need the value to be dynamic as the leave year value can change.

Before digging deeper into your problem, let me say that you shouldn't mutate the store variable directly, but use the provided set or update method. This avoids hard-to-debug bugs:
if (result === -1) {
dbLeaveYear.set(() => `${parseInt(twoDigitYear, 10) - 1}${twoDigitYear}`);
} else {
dbLeaveYear.set(`${twoDigitYear}${parseInt(twoDigitYear, 10) + 1}`);
}
With that out of the way, the problem seems to be that your auto-subscribe to the store is not ideal for your use case. You need to use the subscribe property for that:
<script>
import { dbLeaveYear } from "../stores/store.js"
import { onDestroy, onMount } from "svelte"
let yearValue;
// Needed to avoid memory leaks
let unsubscribe
onMount(() => {
unsubscribe = dbLeaveYear.subscribe(value => yearValue = value.slice(0, 2));
})
onDestroy(unsubscribe);
</script>
Another thing that could cause your problem is a race condition. So the update from the parent component is not finished when the child renders. Then you would need to add a sanity check in the rendering child component.

The answer here is a combination of Sapper preload and the ability to export a function from a store.
in store.js export the writable store for the variable you want and also a function that will work out the value and set the writable store:
export let dbLeaveYear = writable('');
export function getCurrentLeaveYear() {
const today = new Date();
const currYear = today.getFullYear();
const twoDigitYear = currYear.toString().slice(-2);
const cutoffDate = `${twoDigitYear}-04-01`
const result = compareAsc(today, new Date(cutoffDate));
if (result === -1) {
dbLeaveYear.set(`${parseInt(twoDigitYear, 10) - 1}${twoDigitYear}`);
} else {
dbLeaveYear.set(`${twoDigitYear}${parseInt(twoDigitYear, 10) + 1}`);
}
}
In the top-level .svelte file, use Sapper's preload() function inside a "module" script tag to call the function that will work out the value and set the writable store:
<script context="module">
import {getCurrentLeaveYear} from '../stores/store'
export async function preload() {
getCurrentLeaveYear();
}
</script>
And then in the component .svelte file, you can import the store variable and because it has been preloaded it will be available in the <script> tag:
<script>
import {dbLeaveYear} from '../stores/store'
$: startDate = `20${$dbLeaveYear.slice(0, 2)}`
$: endDate = `20${$dbLeaveYear.slice(-2)}`
</script>

Related

How to determine if "click" or "box-select" was used with Streamlit/Plotly to return data from chart to Streamlit

I'm not a Javascript/Typescript/React dev. I'm hacking my way through this for a work project.
I'm using Streamlit, with plotly.
I'm hacking the basic code from streamlit-plotly-events.
I was trying to have the click or box-select information passed back with the data selected via the plotlyEventHandler() (see code below.) However, both this.props.args["click_event"] and this.props.args["select_event"] are true, regardless of whether you use box-select in the plotly chart, or click a single data point in the chart.
I thought of assuming if there is only one data point, then it was a click - but you can box select only one data point.
// import React, {useState,useEffect} from "react"
import React, { ReactNode } from "react"
//import React from "react"
import {
StreamlitComponentBase,
withStreamlitConnection,
Streamlit,
// ComponentProps,
} from "streamlit-component-lib"
import Plot from "react-plotly.js"
class StreamlitPlotlyEventsCapture extends StreamlitComponentBase {
public render = (): ReactNode => {
// Pull Plotly object from args and parse
const plot_obj = JSON.parse(this.props.args["plot_obj"]);
const override_height = this.props.args["override_height"];
const override_width = this.props.args["override_width"];
// Event booleans
const click_event = this.props.args["click_event"];
const select_event = this.props.args["select_event"];
const hover_event = this.props.args["hover_event"];
Streamlit.setFrameHeight(override_height);
return (
<Plot
data={plot_obj.data}
layout={plot_obj.layout}
config={plot_obj.config}
frames={plot_obj.frames}
onClick={click_event ? this.plotlyEventHandler : function(){}}
onSelected={select_event ? this.plotlyEventHandler : function(){}}
onHover={hover_event ? this.plotlyEventHandler : function(){}}
style={{width: override_width, height: override_height}}
className="stPlotlyChart"
/>
)
}
/** Click handler for plot. */
private plotlyEventHandler = (data: any) => {
// Build array of points to return
var clickedPoints: Array<any> = [];
//const util = require('util')//#33333 used with util.inspect(arrayItem) below
// I dont know why we can't directly use "this.variables" in the clickedPoints.push
// but we can't, so we create the variables here.
var wasClicked = this.props.args["click_event"];
var wasSelected = this.props.args["select_event"];
var wasHovered = this.props.args["hover_event"];
data.points.forEach(function (arrayItem: any) {
// console.log(util.inspect(arrayItem, {maxArrayLength: null, depth:null }))
clickedPoints.push({
// I dont know why we can't directly use "this.variables" here, but we can't
// so we use the variables created above.
clicked:wasClicked,
selected:wasSelected,
hovered:wasHovered,
x: arrayItem.x,
y: arrayItem.y,
curveNumber: arrayItem.curveNumber,
pointNumber: arrayItem.pointNumber,
pointIndex: arrayItem.pointIndex
})
});
// Return array as JSON to Streamlit
Streamlit.setComponentValue(JSON.stringify(clickedPoints))
}
}
export default withStreamlitConnection(StreamlitPlotlyEventsCapture)

Meteor Tabular not reacting to ReactiveDict's values changing

I'm using the great Tabular package. https://github.com/Meteor-Community-Packages/meteor-tabular.
I'm making use of the client-side selector helper to Reactively change my table by having Server modify the query for my dataset.
I have multiple HTML inputs that act as filters and am populating a ReactiveDict with the values. A search-button click event triggers the ReactiveDict to get populated with an Object using .set
Initialization of ReactiveDict
Template.tbl.onCreated(function () {
const self = this;
self.filters = new ReactiveDict({});
});
Population of ReactiveDict
'click #search-button'(e, template) {
//clear to 'reset' fields in ReactiveDict that could've been cleared by User
template.filters.clear();
const searchableFields = getSearchableFields();
//Initialize standard JS Obj that ReactiveDict will then be set to
const filterObj = {};
//Loop through search fields on DOM and populate into Obj if they have a val
for (let field of searchableFields) {
const value = $(`#${field}-filter`).val();
if (value) {
filterObj[field] = new RegExp(escapeStringRegex(value.trim()), 'i');
}
}
if (Object.keys(filterObj).length) {
template.filters.set(filterObj);
}
},
Selector Helper
selector: () => {
const filters = Template.instance().filters.all();
const selector = { SOME_DEFAULT_OBJ, ...filters };
return selector;
},
I'm noticing the server doesn't notice any changes from a ReactiveDict if all keys remain the same.
I'm testing this by logging in the serve-side's changeSelector md and verifying that my logging does not occur if just a value in selector has changed.
Is there a solution to this?
I.e. {foo:'foo'} to {foo:'bar'} should reactively trigger the server to re-query but it does not. But {foo:'foo'} to {bar:'bar'} would get triggered.
Is this an issue with how I'm using the ReactiveDict or is this on the Tabular side?
Thanks

Why does my for loop only goes through once when i call function inside it?

I got list of videos from API, it has list of urls fo thumbnail and i would like to combine thumbnails of each video to gif. When i loop through videos and don't generate gifs it goes through 5 times as expected, but if i include function that should generate gifs it only goes through once, without any errors. I have no idea what is happening
I'm using node.js, discord.js, get pixels and gif-encoder modules to generate thumbnails.
for(i=0;i<5;i++){
generateThumbnail(data[i].video.video_id,data[i].video.thumbs,function(){
var tags = '';
for(t=0;t<data[i].video.tags.length;t++){
tags = tags + data[i].video.tags[t].tag_name+', ';
}
fields = [
{name:data[i].video.title,
value:value},
{name:'Tags',
value:tags}
]
msg.channel.send({embed: {
color: 3447003,
thumbnail: {
"url": ""
},
fields: fields,
}});
});
}
function generateThumbnail(id,images,fn){
var pics = [];
console.log(id)
var file = require('fs').createWriteStream(id+'.gif');
var gif = new GifEncoder(images[0].width, images[0].height);
gif.pipe(file);
gif.setQuality(20);
gif.setDelay(1000);
gif.setRepeat(0)
gif.writeHeader();
for(i=0;i<images.length;i++){
pics.push(images[i].src)
}
console.log(pics)
addToGif(pics,gif);
fn()
}
var addToGif = function(images,gif, counter = 0) {
getPixels(images[counter], function(err, pixels) {
gif.addFrame(pixels.data);
gif.read();
if (counter === images.length - 1) {
gif.finish();
} else {
addToGif(images,gif, ++counter);
}
})
}
if i dont use GenerateThumbnail function it goes through 5 times as expected and everything works fine, but if i use it it goes through only once, and generated only 1 gif
Use var to declare for vars. Ie for(var i=0....
If you declare vars without var keyword, they are in the global scope. ..... and you are using another i var inside the function but now it is the same var from the outer for loop.

Why some of the variables are not changed in the nested tag, in RiotJs?

I have a simple nested tag:
<nested-tag>
<p>myTitle: {myTitle}</p>
<p>{myKeyword}</p>
this.myTitle = opts.title;
this.myKeyword = opts.keyword;
</nested-tag>
You can see I assign the opts.title and keyword to two new variable myTitle and myKeyword.
Then I use it in a loop of a parent tag:
<my-tag>
<input type="text" onkeyup={search} value={keyword} />
<ul>
<li each={items}>
<nested-tag title={title} keyword={parent.keyword}></nested-tag>
</li>
</ul>
this.keyword = ""
var initItems = [{ title: "aaaa"}, { title: "bbbb"} ]
this.items = initItems
this.search = function(event) {
this.keyword = event.target.value;
this.items = initItems.filter((item) => item.title.indexOf(this.keyword) >=0 );
}
</my-tag>
You can see I passed the parent.keyword to nested-tag as keyword variable.
When I input something to the text input, the keyword will be changed, so the <nested-tag> will be recreated with the new parent.keyword.
But it's not, the {myKeyword} of nested-tag is always empty. I have to rewrite it with directly opts.keyword invocation:
<nested-tag>
<p>opts.title</p>
<p>opts.keyword</p>
</nested-tag>
And it's working well now.
I'm not sure why and how to fix it? Do I have to always use opts.xxx in the nested tags?
A live demo is here:
http://jsfiddle.net/3jsay5dq/10/
you can type something to the text input to see the result
The javascript in your component nested-tag gets run when instantiating the component. So, when the component is getting generated, the myTitle and myKeyword will be initialized with whatever opts are passed in. But, on update, the myTitle and myKeyword are still pointing to the values set during instantiation. The cleanest way to go about it is to use opts[key] as they will always reflect what is being passed to the component. If you insist on using your own local properties, then you could modify your component like this:
<nested-tag>
<p>myTitle: {myTitle}</p>
<p>{myKeyword}</p>
// this will run every time there is an update either internally or from a passed opts
this.on('update', () => {
this.myTitle = this.opts.title;
this.myKeyword = this.opts.keyword;
})
// this will only run once during instantiation
this.myTitle = opts.title;
this.myKeyword = opts.keyword;
/*
// could be refactored to
this.setMyProps = () => {
this.myTitle = this.opts.title;
this.myKeyword = this.opts.keyword;
}
// bind it to update function
this.on('update', this.setMyProps)
// run once for instantiation
this.setMyProps()
*/
</nested-tag>

Extending or modifying the SharePoint Datasheet view

Has anyone discovered a way to extend or modify the functionality of the SharePoint Datasheet view (the view used when you edit a list in Datasheet mode, the one that looks like a basic Excel worksheet)?
I need to do several things to it, if possible, but I have yet to find a decent non-hackish way to change any functionality in it.
EDIT: An example of what I wish to do is to enable cascading filtering on lookup fields - so a choice in one field limits the available choices in another. There is a method to do this in the standard view form, but the datasheet view is completely seperate.
Regards
Moo
I don't think you can modify it in any non-hackish way, but you can create a new datasheet view from scratch. You do this by creating a new ActiveX control, and exposing it as a COM object, and modifying the web.config file to make reference to the new ActiveX control.
There's an example here:
Creating a custom datasheet control.
Actually, you can do this. Here is a code snippet I stripped out of someplace where I am doing just what you asked. I tried to remove specifics.
var gridFieldOverrideExample = (function (){
function fieldView(ctx){
var val=ctx.CurrentItem[curFieldName];
var spanId=curFieldName+"span"+ctx.CurrentItem.ID;
if (ctx.inGridMode){
handleGridField(ctx, spanId);
}
return "<span id='"+spanId+"'>"+val+"</span>";
}
function handleGridField(ctx, spanID){
window.SP.SOD.executeOrDelayUntilScriptLoaded(function(){
window.SP.GanttControl.WaitForGanttCreation(function (ganttChart){
var gridColumn = null;
var editID = "EDIT_"+curFieldName+"_GRID_FIELD";
var columns = ganttChart.get_Columns();
for(var i=0;i<columns.length;i++){
if(columns[i].columnKey == curFieldName){
gridColumn = columns[i];
break;
}
}
if (gridColumn){
gridColumn.fnGetEditControlName = function(record, fieldKey){
return editID;
};
window.SP.JsGrid.PropertyType.Utils.RegisterEditControl(editID, function (ctx) {
editorInstance = new SP.JsGrid.EditControl.EditBoxEditControl(ctx, null);
editorInstance.NewValue = "";
editorInstance.SetValue = function (value) {
_cellContext = editorInstance.GetCellContext();
_cellContext.SetCurrentValue({ localized: value });
};
editorInstance.Unbind = function () {
//This happens when the grid cell loses focus - hide controls here, do cleanup, etc.
}
//Below I grabbed a reference to the original 'BindToCell' function so I can prepend to it by overwriting the event.
var origbtc = editorInstance.BindToCell;
editorInstance.BindToCell = function(cellContext){
if ((cellContext.record) &&
(cellContext.record.properties) &&
(cellContext.record.properties.ID) &&
(cellContext.record.properties.ID.dataValue)){
editorInstance.ItemID = cellContext.record.properties.ID.dataValue;
}
origbtc(cellContext);
};
//Below I grabbed a reference to the original 'OnBeginEdit' function so I can prepend to it by overwriting the event.
var origbte = editorInstance.OnBeginEdit;
editorInstance.TargetID;
editorInstance.OnBeginEdit = function (cellContext){
this.TargetID = cellContext.target.ID;
/*
. . .
Here is where you would include any custom rendering
. . .
*/
origbte(cellContext);
};
return editorInstance;
}, []);
}
});
},"spgantt.js");
}
return{
fieldView : fieldView
}
})();
(function () {
function OverrideFields(){
var overrideContext = {};
overrideContext.Templates = overrideContext.Templates || {};
overrideContext.Templates.Fields = {
'FieldToOverride' : {
'View': gridFieldOverrideExample.fieldView
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
}
ExecuteOrDelayUntilScriptLoaded(OverrideFields, 'clienttemplates.js');
})();
Also, there are a couple of other examples out there. Sorry, I don't have the links anymore:

Resources