use prop in another prop from same svelte component - javascript

I've just started learning svelte in the last hour so forgive me if this question is stupid or already answered. I tried searching but I don't even know the parlance to ask the question well.
Based on this svelte.dev tutorial link on spread props, my question is as follows.
How do I modify this to be something like this code.
<script>
import Info from './Info.svelte';
const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.dev',
// this is the modification
npm: 'https://www.npmjs.com/package/' + {name},
// I've also tried:
npm: 'https://www.npmjs.com/package/{name}',
};
</script>
<Info {...pkg}/>
This is the other modified file.
<script>
export let name;
export let version;
export let speed;
export let website;
export let npm;
</script>
<p>
The <code>{name}</code> package is {speed} fast.
Download version {version} from <a href={npm}>npm</a>
and <a href={website}>learn more here</a>
</p>
I'm trying to use a prop in the declaration of another prop exported from the same component.
Seems like it's easy but I'm missing something.
**** Edit *****
Based on Thomas Hennes answer I realized I had the foundation of the question wrong. What I needed to understand was the control flow structure of Svelte, which rendered this formulation redundant. I had a flawed model of a component as some sort of function that took inputs and could also return outputs that could be used in other components. But I'm starting to understand (I think) that it's more just a top-down inheritance model maybe.
TL:DR, Konrads answer was technically correct based on my actual question, but Thomas Hennes answer helped me the most.

Jumping in a little late, but here is my take on your problem and what I feel is a more Svelte-like solution.
npm here is really nothing more than a value statically and systematically derived from name, and as such I feel it is redundant data, meaning that it is not essential that this value should be passed as a prop to child components as those child components could just as easily derive that information from the value of name (which is essential data).
On top of that, Svelte's reactive notation is ideally suited to address such needs.
Here is how I would piece it together:
<script>
import Info from './Info.svelte';
const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.dev',
// none of this is needed, redundant info
// npm: 'https://www.npmjs.com/package/' + {name},
// npm: 'https://www.npmjs.com/package/{name}',
};
</script>
<Info {...pkg}/>
And in Info.svelte:
<script>
export let name;
export let version;
export let speed;
export let website;
// Reactive code, npm will update whenever name changes
$: npm = `https://www.npmjs.com/package/${name}`
</script>
<p>
The <code>{name}</code> package is {speed} fast.
Download version {version} from <a href={npm}>npm</a>
and <a href={website}>learn more here</a>
</p>
In general, if you find one prop can/should be derived from another existing prop, then that derived prop is redundant and should not be passed as a prop but instead derived as needed in the component(s) that consume the prop.

Once again, it's explained here Self-references in object literals / initializers
<script>
import Info from './Info.svelte';
const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.dev',
get npm() { return 'https://www.npmjs.com/package/' + this.name },
// or
get npm() { return `https://www.npmjs.com/package/${this.name}` },
};
</script>
<Info {...pkg}/>
Another solution:
<script>
import Info from './Info.svelte';
const name = 'svelte'
const pkg = {
name,
version: 3,
speed: 'blazing',
website: 'https://svelte.dev',
npm: 'https://www.npmjs.com/package/' + name,
// or
npm: `https://www.npmjs.com/package/${name}`,
};
</script>
<Info {...pkg}/>

After A lot of head scratching🤔.... I finally understand what you want to do.
name is a property of the object pkg, just writing name means you're calling an undeclared variable called name.
However, calling the name property inside the pkg object will also not work, you'll get an call before initialization error.
Instead you can turn the npm property into a function that can access the pkg object properties and return them. Remember you'll call npm as npm() in Info.svelte now because its a function. Please see my example here. Also, do consider learning more about data types in JavaScript...
Happy coding 😃

Related

Rails 6 Webpacker calling javascript function from Rails view

I have following structure for Javascript in my Rails 6 app using Webpacker.
app/javascript
+ packs
- application.js
+ custom
- hello.js
Below shown is the content in the above mentioned JS files
app/javascript/custom/hello.js
export function greet(name) {
console.log("Hello, " + name);
}
app/javascript/packs/application.js
require("#rails/ujs").start()
require("jquery")
require("bootstrap")
import greet from '../custom/hello'
config/webpack/environment.js
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
Popper: ['popper.js', 'default']
})
)
module.exports = environment
Now in my Rails view I am trying to use the imported function greet like shown below
app/views/welcome/index.html.haml
- name = 'Jignesh'
:javascript
var name = "#{name}"
greet(name)
When I load the view I am seeing ReferenceError: greet is not defined error in browser's console.
I tried to search for a solution to this problem and found many resources on web but none turned out to help me. At last when I was drafting this question in the suggestions I found How to execute custom javascript functions in Rails 6 which indeed is close to my need however the solution shows a workaround but I am looking for a proper solution for the need because I have many views which needs to pass data from Rails view to JS functions to be moved custom files under app/javascript/custom folder.
Also I would highly appreciate if anybody can help me understand the cause behind the ReferenceError I am encountering.
Note:
I am not well-versed in Javascript development in Node realm and also new to Webpacker, Webpack, Javascript's modules, import, export, require syntax etc so please bear with me if you find anything silly in what I am asking. I have landed up in above situation while trying to upgrade an existing Rails app to use version 6.
Webpack does not make modules available to the global scope by default. That said, there are a few ways for you to pass information from Ruby to JavaScript outside of an AJAX request:
window.greet = function() { ... } and calling the function from the view as you have suggested is an option. I don't like have to code side effects in a lot of places so it's my least favorite.
You could look at using expose-loader. This would mean customizing your webpack config to "expose" selected functions from selected modules to the global scope. It could work well for a handful of cases but would get tedious for many use cases.
Export selected functions from your entrypoint(s) and configure webpack to package your bundle as a library. This is my favorite approach if you prefer to call global functions from the view. I've written about this approach specifically for Webpacker on my blog.
// app/javascript/packs/application.js
export * from '../myGlobalFunctions'
// config/webpack/environment.js
environment.config.merge({
output: {
// Makes exports from entry packs available to global scope, e.g.
// Packs.application.myFunction
library: ['Packs', '[name]'],
libraryTarget: 'var'
},
})
// app/views/welcome/index.html.haml
:javascript
Packs.application.greet("#{name}")
Take a different approach altogether and attach Ruby variables to a global object in your controller, such as with the gon gem. Assuming you setup the gem per the instructions, the gon object would be available both as Ruby object which you can mutate server-side and in your JavaScript code as a global variable to read from. You might need to come up with some other way to selectively call the greet function, such as with a DOM query for a particular selector that's only rendered on the given page or for a given url.
# welcome_controller.rb
def index
gon.name = 'My name'
end
// app/javascript/someInitializer.js
window.addEventListener('DOMContentLoaded', function() {
if (window.location.match(/posts/)) {
greet(window.gon.name)
}
})
#rossta Thanks a lot for your elaborate answer. It definitely should be hihghly helpful to the viewers of this post.
Your 1st suggestion I found while searching for solution to my problem and I did referenced it in my question. Like you I also don't like it because it is sort of a workaround.
Your 2nd and 3rd suggestions, honestly speaking went top of my head perhaps because I am novice to the concepts of Webpack.
Your 4th approach sounds more practical to me and as a matter of fact, after posting my question yesterday, along similar lines I tried out something and which did worked. I am sharing the solution below for reference
app/javascript/custom/hello.js
function greet(name) {
console.log("Hello, " + name)
}
export { greet }
app/javascript/packs/application.js
require("#rails/ujs").start()
require("bootstrap")
Note that in above file I removed require("jquery"). That's because it has already been made globally available in /config/webpack/environment.js through ProvidePlugin (please refer the code in my question). Thus requiring them in this file is not needed. I found this out while going through
"Option 4: Adding Javascript to environment.js" in http://blog.blackninjadojo.com/ruby/rails/2019/03/01/webpack-webpacker-and-modules-oh-my-how-to-add-javascript-to-ruby-on-rails.html
app/views/welcome/index.html.haml
- first_name = 'Jignesh'
- last_name = 'Gohel'
= hidden_field_tag('name', nil, "data": { firstName: first_name, lastName: last_name }.to_json)
Note: The idea for "data" attribute got from https://github.com/rails/webpacker/blob/master/docs/props.md
app/javascript/custom/welcome_page.js
import { greet } from './hello'
function nameField() {
return $('#name')
}
function greetUser() {
var nameData = nameField().attr('data')
//console.log(nameData)
//console.log(typeof(nameData))
var nameJson = $.parseJSON(nameData)
var name = nameJson.firstName + nameJson.lastName
greet(name)
}
export { greetUser }
app/javascript/packs/welcome.js
import { greetUser } from '../custom/welcome_page'
greetUser()
Note: The idea for a separate pack I found while going through https://blog.capsens.eu/how-to-write-javascript-in-rails-6-webpacker-yarn-and-sprockets-cdf990387463
under section "Do not try to use Webpack as you would use Sprockets!" (quoting the paragraph for quick view)
So how would you make a button trigger a JS action? From a pack, you add a behavior to an HTML element. You can do that using vanilla JS, JQuery, StimulusJS, you name it.
Also the information in https://prathamesh.tech/2019/09/24/mastering-packs-in-webpacker/ helped in guiding me to solve my problem.
Then updated app/views/welcome/index.html.haml by adding following at the bottom
= javascript_pack_tag("welcome")
Finally reloaded the page and the webpacker compiled all the packs and I could see the greeting in console with the name in the view.
I hope this helps someone having a similar need like mine.

Beautiful fix for circular dependecies problem in Javascript / Typescript

I just stumbled upon the circular dependency problem when building a project I am working on: a small ORM, for learning purposes.
Minimal project to reproduce the problem can be found here. Here is an even simpler overview:
Article:
import { ManyToMany } from './ManyToMany';
import { Tag } from './Tag';
export class Article {
#ManyToMany(Tag)
tags: Tag[] = [];
}
ManyToMany:
//no imports
export function ManyToMany(entity) {
…
}
Tag:
import { ManyToMany } from './ManyToMany';
import { Article } from './Article';
export class Tag {
#ManyToMany(Article)
articles: Article[] = [];
}
It is important that the code is not changed too much or at all because this affects the DX. I don't want the users of this library to create additional hacky files to fix this problem.
I found a list of discussions but none of them are elegant:
https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
https://github.com/Microsoft/TypeScript/issues/20361
Circular Type References in TypeScript
https://spin.atomicobject.com/2018/06/25/circular-dependencies-javascript/
The best solution that I could find is using the internal module pattern. This and all other solutions are just hacks and workarounds to this problem and none actually fixes it.
Is there a better, more elegant solution to this without creating additional files or move code around?
As per Bergi's comment, the solution that would work is to make the reference lazy i.e. pass a function returning the entity and do not evaluate it immediately (during the property decoration) but later when all the modules are loaded, the classes initialized, and the first instance is being created.
I have tried to implement the same https://stackblitz.com/edit/typescript-f5rm9u here. Please check the implementation.
ManyToMany
export function ManyToMany(entity=() => entity) {
return function(target, name) {
console.log(entity.name);
};
}
Answer credit: #Bergi

Checking for default values in Typescript

Is there any way in typescript / typescript-eslint to render an error when an optional parameter does not have a a default value? I am trying to convert my React codebase from JSX to TSX and no longer having the warnings about not having defaultProps defined is worrisome. Thanks.
bad: title does not have default prop value
import * as React from 'react';
interface Props {
title?: string;
}
const SampleComponent: React.FC<Props> = ({ title }) => (
<h1>
{title && <p>{title}</p>}
</h1>
);
export default SampleComponent;
good: title has default prop value
import * as React from 'react';
interface Props {
title?: string;
}
const SampleComponent: React.FC<Props> = ({ title = 'foo' }) => (
<h1>
{title && <p>{title}</p>}
</h1>
);
export default SampleComponent;
This isn't something TypeScript will do for you, so there's no reliable & easy option available.
However, with a little work it is something that could be implemented as an ESLint rule. Linting rules are given the abstract syntax tree of your code (AST - a data structure describing the code of a program), and can then run checks against it, such as getting every parameter, filtering to just the optional parameters, and then checking if those all have a default value.
To actually do this, I would suggest:
Set up ESLint with the ESLint-TypeScript plugin in your project
Read this introduction to writing a custom ESLint rule: https://blog.yonatan.dev/writing-a-custom-eslint-rule-to-spot-undeclared-props/ (note that this is looking at the JavaScript AST, not TypeScript, but it's very similar, and pure JS is a good starting point)
Take a look at simple existing TS linting rules like no-parameter-properties (no private/public/etc properties on constructor arguments), and make sure you understand how they work
Have a go at writing your own
Note that tslint also exists, as a purely TypeScript-focused linting tool. This may be an option, and historically this has been more popular for TS linting, but it's now deprecated in favour of eslint-typescript, so I would avoid starting with it nowadays.

Document function that returns object property by parameter

I'm making an Api repository for my Vue.js application, following this article.
Thing is, I like to document my functions so I have better code completion on VSCode. I typically use jsDoc for this.
I'm stuck here:
import DiarioEscolarRepository from './diarioEscolarRepository';
import PeriodoAvaliativoRepository from './periodoAvaliativoRepository';
import AtividadeAvaliativaRepository from './atividadeAvaliativaRepository';
import CorteEtarioRepository from './corteEtarioRepository';
const repositories = {
diarioEscolar: DiarioEscolarRepository,
periodoAvaliativo: PeriodoAvaliativoRepository,
atividadeAvaliativa: AtividadeAvaliativaRepository,
corteEtario: CorteEtarioRepository,
};
export default const RepositoryFactory = {
get(name){
return repositories[name];
}
};
I need to make it so the editor understands that the get function is a simple acessor to the repositories object.
I tried using #typedef and #type, but none of them worked properly.
I tried something like #returns {repositories.name}, but is also does not work.
Is there a way to document this?
I also thought about using a typescript definition file but I never did it, so I don't know where to begin.

How to properly use es6 classes in different files by importing them in Meteor?

I have recently discovered Meteor and I am struggling with using ES6 classes and imports in a new Meteor project. What I want to do is to have a complex structure of classes, which methods get called from Meteor events/methods/helpers. I've added Babel.js to the project by writing a command $ meteor add grigio:babel and it works properly.
Example of what I am trying to achieve:
in server/models/article.js:
class Article {
static all() {
//returns all articles from db
}
}
in server/methods/articles.js:
Meteor.methods({
allArticles: {
Article.all();
}
})
Having just that raises ReferenceError: Article is not defined in a methods file, which is adequate. So I have got three options: write all classes in one file, append all classes to a global object or use a good module system like Browserify. Obviously, third option is better.
But how do I use that? Babel converts export, import into Browserify by default and Meteor raises a require is not defined error on page refresh. After googling the problem I didn't find a clear solution on how to add Browserify to Meteor. Should I add a npm packages support to Meteor, add a npm package of browserify and add it manually to Meteor on every page where I import/export anything? Or should I use a completely different approach? How is this task usually handled in Meteor? Thank you!
I was reading about this earlier and found this issue on github that may help.
Essentially just assign the class to a variable that is exposed to both the client and server (lib/both/etc depends on your file structure). Like so:
Article = class Article {...}
Seems to be the best solution at the moment.
The way I do this is to collect objects together into various namespaces, for example:
// Global
Collections = {};
class Article {
static all() {
//returns all articles from db
}
}
_.extend(Collections, { Article });
Then to avoid having to use Collections.Article everywhere I can use the following in the file I need to access Article in:
// Make `Article` available
let { Article } = Collections;
I am using Meteor 1.4.1.1 and the error remains, when reproducing your approach. However, there are some new ways to use es6 classes now:
1. Export your class as a constant (e.g. for use as a singleton object):
class MyModuleInternalClassName {
//... class internals
}
export const PublicClassName = new MyModuleInternalClassName();
You can import this one via
import {PublicClassName} from 'path/to/PublicClassFileName.js';
2. Export your class directly as the module's default
export default class PublicClassName {
//... class internals
}
and then import it (as with the above one) as the following
import {PublicClassName} from from 'path/to/PublicClassFileName.js';
let myInstance = new PublicClassName();
+++++++++++++++++++++++++++++++++
Regarding the question of OP and the error, you can try something like this:
Article.js
class InternalArticle {
constructor(){
//setup class
}
all() {
//returns all articles from db
}
register(article){
//add article to db
}
}
export const Article = new InternalArticle();
Import and use the Singleton
import {Article} from 'path/to/Article.js';
//either register some article
Article.register(someArticle);
//or get all your articles
const allArticles = Article.all();

Categories