Meteor 1.5 : Dynamic Import for Blaze - meteor

I am having two questions:
1) I want to use Meteor 1.5 Dynamic Import for Blaze but all the examples and tutorials are given for React. So I am confused how exactly it can be used . Can anyone give examples of it.
2) I am using packages from atmospherejs.com like amcharts which I only need at Admin Dashboard side. How to dynamically import them?
Thanks in Advance!
UPDATE(Solution):
Below is homepage.html (parent template)
<template name="homepage">
Homepage Content
{{> Template.dynamic template=content}}
</template>
login.html (child template)
<template name="login">
You're logged in!
</template>
login.js
import '../homepage/homepage.js';
import './login.html';
API = function () {
BlazeLayout.render("homepage",{content: 'login'});
}
export { API }
main.js
LoadLogin = function () {
import('/imports/modules/common/login/login.js').then(function (api) {
api.API();
})
}
/lib/route.js
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
FlowRouter.route('/', {
name: 'homepage',
action() {
LoadLogin();
}
});

I am developing my own admin panel, Meteor Candy, to be driven by dynamic imports, so I am happy to share how I got it working.
First, we have the view.html:
<template name="admin">
Admin
</template>
Second, we have our JS logic:
import { Template } from 'meteor/templating';
import { Meteor } from 'meteor/meteor';
import { Blaze } from 'meteor/blaze';
import './view.html';
API = {}
API.render = function () {
Blaze.render(Template.admin, document.body);
}
export { API }
Finally, we just need to import that code and trigger our Template to be rendered into the page:
openAdmin = function () {
import('./imports/admins').then(function (api) {
api.render()
})
}
Once something runs the openAdmin() function, the templates will be imported from the server and the render function will be called.

The basic technique for dynamically importing modules in Meteor 1.5 using Blaze is as follows:
Template.theTemplate.events({
'click button'(event, instance) {
import("foo").then(Foo => {
console.log(Foo);
});
}
});
Make sure you take a close look at how your module is then imported, because apparently some refactoring may be needed when calling it in your code. For example, using "zxcvbn":
WAS
const result = zxcvbn(pwd);
IS
const result = zxcvbn.default(pwd);

It is pretty straight forward using example link https://github.com/thesaucecode/meteor-amcharts-example/blob/master/client/example2.js, you just have to write the code inside Template.MYTEMPLATE.onRendered(function(){});
On top of that you can use var chart as reactive-var.

Related

Retrieve a Font Awesome or Bootstrap Icon (or React Component) from MongoDB into React

I'm building a database of hot sauce products in MongoDB Atlas for my React App. One attribute of the sauces is heat intensity. I'd like to use a bootstrap or font awesome icon to illustrate this. One flame, two flames, three flames...etc. I figured out a handful of ways to code it if I were storing the products statically in my React app, but since I want to have a dynamic inventory stock number attached to each product, I'm storing all the product descriptions in the database.
My question is how can I store the icon, in any of it's formats (bootstrap icon for example):
Unicode: U+F7F6
CSS: \F7F6
JS: \uF7F6
HTML: &#xF7F6
and then retrieve it.
Everything I've tried just errors or gives me a plain string in my div.
I'd also be all ears if you have a way to store and retrieve react components, as I can go the svg imported as a component route.
Like if I have
import { ReactComponent as Pep5 } from "../assets/pep5.svg";
import { ReactComponent as Pep4 } from "../assets/pep4.svg";
import { ReactComponent as Pep3 } from "../assets/pep3.svg";
and can somehow store and retrieve
<Pep4 className="heatScale"/>
My code for getting all the records in my db is:
recordRoutes.route("/sauce").get(function (req, res) {
let db_connect = dbo.getDb("products");
db_connect
.collection("sauces")
.find({})
.toArray(function (err, result) {
if (err) throw err;
res.json(result);
});
});
module.exports = recordRoutes;
All the other parts of my "product" are working great and coming across perfectly.
I figured out the boneheaded method I was trying to accomplish the svg version.
TL;DR I don't need to store the React Component code in the database, only an identifier I can later use to conditionally return the React Component I want from a function.
Imports:
import { ReactComponent as Pep5 } from "../assets/pep5.svg";
import { ReactComponent as Pep4 } from "../assets/pep4.svg";
import { ReactComponent as Pep3 } from "../assets/pep3.svg";
import { ReactComponent as Pep2 } from "../assets/pep2.svg";
import { ReactComponent as Pep1 } from "../assets/pep1.svg";
Function:
function PepEval(heat) {
const value = JSON.stringify(heat);
console.log(value);
if (value.includes("1")) {
return <Pep1 className="peppers" />;
} else if (value.includes("2")) {
return <Pep2 className="peppers" />;
} else if (value.includes("3")) {
return <Pep3 className="peppers" />;
} else if (value.includes("4")) {
return <Pep4 className="peppers" />;
} else if (value.includes("5")) {
return <Pep5 className="peppers" />;
}
return "Something Broke";
}
Div that calls the function:
<div className="itemHeat">
<PepEval value={{ heat }} />
</div>
I'm sure JS has a Select Case type thing instead of all the else ifs but I wanted to get it working.
If someone has a better method for evaluating the JSON object that I'm bringing in, I'm all ears.

Vue3 Add html to existing project

I’ve built my first project and have run the build process. I have my index.html file and it works if opened directly.
I’ve copied the code into an existing html page and the initial page load is fine. However, when props get updated, binding (v-if statements) no longer works.
Any help would be great
Edit with code example
<script>
import { ref } from "vue";
import Determining from './Determining.vue'
import Ready from './Ready.vue'
export default {
components: {
'Determining': Determining,
'Ready': Ready,
},
setup() {
let checkout = ref({
state: 'determining',
});
return {
checkout,
};
},
created() {
this.checkout.state = 'ready';
console.log("I am getting here");
}
}
</script>
<template>
<Determining v-if="checkout.state == 'determining'" />
<Ready v-if="checkout.state == 'ready'" />
</template>
The determining state is shown when the page first loads. The console log is firing in setup, but Ready component is not showing
Progress
I've narrowed it down to other javascript running on the page.
Any javascript, even just
<script>console.log("hello");</script>
Is enough to break it.
Other than adding additional javascript to Vue, is there anyway around it?
if I'm not wrong, you can't access composition api or setup() variables in options api (such as created, mounted, data, methods etc). You can use beforeMount as #Thomas commented or onMounted by importing it from vue, for the example:
<script>
import { ref, beforeCreate } from "vue";
import Determining from './Determining.vue'
import Ready from './Ready.vue'
export default {
components: {
'Determining': Determining,
'Ready': Ready,
},
setup() {
beforeCreate(()=> {
checkout.state.value = 'ready';
console.log("I am getting here");
})
let checkout = ref({
state: 'determining',
});
return {
checkout,
};
}
}
</script>
<template>
<Determining v-if="checkout.state == 'determining'" />
<Ready v-if="checkout.state == 'ready'" />
</template>
if you want it more simple, you can use script sugar setup, this way you don't need to return in stup(). It can make your code simpler but if you want to define props, the approach is different
<script setup>
import { ref, beforeCreate } from "vue";
import Determining from './Determining.vue'
import Ready from './Ready.vue'
beforeCreate(()=> {
checkout.state.value = 'ready';
console.log("I am getting here");
})
let checkout = ref({
state: 'determining',
});
</script>
<template>
<Determining v-if="checkout.state == 'determining'" />
<Ready v-if="checkout.state == 'ready'" />
</template>

CodeMirror on Vue3 has a problem when setValue is kicked

I'm trying to use CodeMirror on Vue3 and the problem occurs when I call doc.setValue().
The Problem is following:
Cursor position is broken when doc.setValue() is called
CodeMirror throws an exception when continuing editing
The exception is here.
Uncaught TypeError: Cannot read property 'height' of undefined
at lineLength (codemirror.js:1653)
at codemirror.js:5459
at LeafChunk.iterN (codemirror.js:5623)
at Doc.iterN (codemirror.js:5725)
at Doc.iter (codemirror.js:6111)
at makeChangeSingleDocInEditor (codemirror.js:5458)
at makeChangeSingleDoc (codemirror.js:5428)
at makeChangeInner (codemirror.js:5297)
at makeChange (codemirror.js:5288)
at replaceRange (codemirror.js:5502)
How should I solve this?
~~~
Versions are CodeMirror: 5.61.1, Vue.js: 3.0.11
My code is following:
index.html
<div id="app"></div>
<script src="./index.js"></script>
index.js
import { createApp } from 'vue';
import App from './App';
const app = createApp(App);
app.mount('#app');
App.vue
<template>
<div>
<button #click="click">Push Me</button>
<textarea id="codemirror"></textarea>
</div>
</template>
<script>
import CodeMirror from 'codemirror/lib/codemirror.js';
import 'codemirror/lib/codemirror.css';
// import codemirror resources
import 'codemirror/addon/mode/overlay.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/gfm/gfm.js';
export default {
data () {
return {
cm: null
}
},
mounted () {
this.cm = CodeMirror.fromTextArea(document.getElementById('codemirror'), {
mode: 'gfm',
lineNumbers: true,
});
},
methods: {
click (event) {
this.cm.getDoc().setValue('foo\nbar');
}
}
}
</script>
Thanks.
UPDATES
First, this problem also occurs when I used replaceRange() with multiline.
Unfortunately, I couldn't find any solution. So I tried to find another way.
My solution is recreating Codemirror instance with a textarea that has new content.
It works well.
// Remove old editor
this.cm.toTextArea();
// Get textarea
const textarea = document.getElementById('codemirror');
// Set new content
textarea.value = 'foo\nbar';
// Create new editor
this.cm = CodeMirror.fromTextArea(textarea, { /** options */ });
I found a method, you can use toRaw to get the original Object from Proxy,and this method can be also used in monaco-editor
import { toRaw } from 'vue'
import CodeMirror from 'codemirror/lib/codemirror.js';
import 'codemirror/lib/codemirror.css';
// import codemirror resources
import 'codemirror/addon/mode/overlay.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/gfm/gfm.js';
export default {
data () {
return {
cm: null
}
},
mounted () {
this.cm = CodeMirror.fromTextArea(document.getElementById('codemirror'), {
mode: 'gfm',
lineNumbers: true,
});
},
methods: {
click (event) {
toRaw(this.cm).setValue('foo\nbar');
}
}
}
Another way,you don't have to define cm in data, just use this.cm
data () {
return {
//cm: null
}
},

Unexpected browserHistory behaviour (Meteor + React + React Router)

(PS: I'm using Meteor + React + React Router. The application structure is not traditional, I'm making a package-esq application, an example is https://github.com/TelescopeJS/Telescope. I'm trying to do dynamic routing with react router and things are not working out well.)
There be something wrong with browserHistory. Navigation refreshes the page. Going back and forth through the browser buttons refreshes the page.
Example of this, with all codes, are here - https://github.com/dbx834/sandbox
React-Router specific codes follow,
In a core package, with a global export, allow registeration of routes and components
...
// ------------------------------------- Components -------------------------------- //
Sandbox.components = {};
Sandbox.registerComponent = (name, component) => {
Sandbox.components[name] = component;
};
Sandbox.getComponent = (name) => {
return Sandbox.components[name];
};
// ------------------------------------- Routes -------------------------------- //
Sandbox.routes = {};
Sandbox.routes.routes = [];
Sandbox.routes = {
routes: [],
add(routeOrRouteArray) {
const addedRoutes = Array.isArray(routeOrRouteArray) ? routeOrRouteArray : [routeOrRouteArray];
this.routes = this.routes.concat(addedRoutes);
},
};
...
In various implementations (domain specific logic, UI, etc), register components and routes
...
import TodoApp from './components/TodoApp.jsx';
Sandbox.registerComponent('TodoApp', TodoApp);
Sandbox.routes.add([
{ name: 'todoAppRoute', path: 'todo-app', component: Sandbox.components.TodoApp },
]);
...
In the main app
import React from 'react';
import { render } from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { Router, browserHistory } from 'react-router';
import App from './components/App.jsx';
import Homepage from './components/Homepage.jsx';
Sandbox.registerComponent('App', App);
Sandbox.registerComponent('Homepage', Homepage);
Meteor.startup(() => {
const AppRoutes = {
path: '/',
component: Sandbox.components.App,
indexRoute: { name: 'home', component: Sandbox.components.Homepage },
childRoutes: Sandbox.routes.routes,
};
console.log(AppRoutes);
render(
<Router routes={AppRoutes} history={browserHistory} />,
document.getElementById('app-root')
);
});
What is wrong?
I uninstalled all npm packages, meteor packages, updated everything, re-installed latest packages, cleaned out all previous builds and everything works now!
There was something weird somewhere.
If anyone finds themselves in a similar situation, you can try this.
Best

Updating a template with a component input

Preface: I'm new to Meteor, Angular, and Typescript, so there is a very real possibility of an XY problem somewhere in here.
I'm working on a simple project management app using Meteor and Angular 2 (using the angular2-meteor package) where the structure (for now) consists of projects which have events. One view is a list of projects. Clicking on a project shows a modal of the project's details, including a list of the project's events. So, three components: ProjectList, ProjectDetails, and ProjectEventsList. ProjectDetails uses a Session variable to know which project to show, and that works. However, the list of events in the modal doesn't update after it is created for the first project clicked on.
ProjectEventsList.ts
import {Component, View} from 'angular2/core';
import {MeteorComponent} from 'angular2-meteor';
import {ProjectEvents} from 'collections/ProjectEvents';
#Component({
selector: 'projectEventsList',
inputs: ['projectId']
})
#View({
templateUrl: '/client/projectEventsList/projectEventsList.html'
})
export class ProjectEventsList extends MeteorComponent {
projectEvents: Mongo.Cursor<ProjectEvent>;
projectId: string;
constructor() {
super();
this.subscribe('projectEvents', this.projectId, () => {
this.autorun(() => {
this.projectEvents = ProjectEvents.find({projectId: this.projectId});
}, true);
});
}
}
As I understand it (though I may be way off here), I'm having difficulty getting autorun to, well, automatically run. I've tried putting a getter and setter on projectId and it does get updated when I click on a project, but the code inside autorun doesn't run after the first click. Things I've tried:
Switching the nesting of subscribe() and autorun().
Adding/removing the autobind argument to both subscribe() and autorun(). I don't really understand what that's supposed to be doing.
Moving the subscribe code to a setter on projectId:
private _projectId: string = '';
get projectId() {
return this._projectId;
}
set projectId(id: string) {
this._projectId = id;
this.subscribe('projectEvents', this._projectId, () => {
this.projectEvents = ProjectEvents.find({projectId: this._projectId});
}, true);
}
When I do this the list stops displaying any items.
If this all seems like it should work, I'll create a small test case to post, but I am hoping that something in here will be obviously wrong to those who know. Thanks!
this.subscribe() and this.autorun() doesn't seem to be part of the Angular component class. If this is an external library you might need to explicitly run it in an Angular zone for change detection to work:
constructor(private zone: NgZone) {
this.subscribe('projectEvents', this.projectId, () => {
this.autorun(() => {
zone.run(() => {
this.projectEvents = ProjectEvents.find({projectId: this.projectId});
});
}, true);
});
}
If you want to subscribe to events fired from the component itself use host-binding
#Component(
{selector: 'some-selector',
host: {'projectEvents': 'projectsEventHandler($event)'}
export class SomeComponent {
projectsEventHandler(event) {
// do something
}
}
I eventually got the setter method working, as shown below. It feels clunky, so I'm hoping there's a cleaner way to do this, but the below is working for me now (i.e., the list of events is updated when the parent component (ProjectList) sends a new projectId to the input.
ProjectEventsList.ts
import {Component, View} from 'angular2/core';
import {MeteorComponent} from 'angular2-meteor';
import {ProjectEvents} from 'collections/ProjectEvents';
#Component({
selector: 'projectEventsList',
inputs: ['projectId']
})
#View({
templateUrl: '/client/projectEventsList/projectEventsList.html'
})
export class ProjectEventsList extends MeteorComponent {
projectEvents: Mongo.Cursor<ProjectEvent>;
set projectId(id: string) {
this._projectId = id;
this.projectEventsSub = this.subscribe('projectEvents', this._projectId, () => {
this.projectEvents = ProjectEvents.find({projectId: this._projectId}, {sort: { startDate: 1 }});
}, true);
}
get projectId() {
return this._projectId;
}
constructor() {
super();
this.subscribe('projectEvents', this.projectId, () => {
this.projectEvents = ProjectEvents.find({projectId: this.projectId});
}, true);
}
}

Resources