Im new to Blazor. Have a js function that uses gojs library. Canvas, data loading are processed dynamically on js side. As I undestood, just adding js script on Host is not enouph and I have to use IJSRuntime. From examples I found it is understandable when calling simple functions with return etc. But I have this:
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make; // for conciseness in defining templates
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
initialContentAlignment: go.Spot.Center,
allowDrop: true, // must be true to accept drops from the Palette
"LinkDrawn": showLinkLabel, // this DiagramEvent listener is defined below
"LinkRelinked": showLinkLabel,
"animationManager.duration": 800, // slightly longer than default (600ms) animation
"undoManager.isEnabled": true // enable undo & redo
});
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function (e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
myPalette =
$(go.Palette, "myPaletteDiv", // must name or refer to the DIV HTML element
{
"animationManager.duration": 800, // slightly longer than default (600ms) animation
nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram
model: new go.GraphLinksModel([ // specify the contents of the Palette
{ category: "Start", text: "Start", figure: "Ellipse" },
{ text: "Step" },
{ text: "Check Trigger", figure: "Diamond" },
{ category: "End", text: "End" }
//{ category: "Comment", text: "Comment" }
])
});
}
}
function load() {
myDiagram.model = go.Model.fromJson(document.getElementById("jsonModel").value);
}
and so on. So, on init my canvas are dynamically built.
on component side I have :
<div onload="init()"></div>
<span style="display: inline-block; vertical-align: top; padding: 5px; width:20%">
<div id="myPaletteDiv" style="border: solid 1px gray; height: 100%">
``` </div>```
``` </span>```
``` <span style="display: inline-block; vertical-align: top; padding: 5px; width:80%">```
``` <div id="myDiagramDiv" style="border: solid 1px gray; height: 100%"></div>```
```</span>```
<textarea style="display:inline-block" id="jsonModel"></textarea>
How can I process all this in Blazor? Tried
[JSInvokable]
protected override Task OnAfterRenderAsync(bool firstRender = true)
{
if (firstRender)
{
return jsRuntime.InvokeVoidAsync
("init").AsTask();
}
return Task.CompletedTask;
}
but it didn`t work.
Thank you in advance for any help
There is a complete sample at: https://github.com/NorthwoodsSoftware/GoJS-Alongside-Blazor
It does this:
protected async override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// This calls the script in gojs-scripts.js
await JSRuntime.InvokeAsync<string>("initGoJS");
}
}
Alas, I am not familiar with Blazor, so I'm not able to answer any questions about it.
Related
I know there must be a more efficient way of doing this, I've done it this way in the past because I haven't had many buttons to track, but I now have about 40 buttons that each update updates a mysql table with either a yes or no, and having 40 individual variables and equivalent if statements seems like bad code.
Something to note is that you can see the function has a 1 e.g. onclick='btnChange(1, this.value);. There are 7 different buttons, and then these 7 buttons repeat for onclick='btnChange(2, this.value);. So one solution I thought of is to have 7 if statements for each button and have variable names for each if statement and then I would only have to declare a lot of variables. SO I wasn't sure if that was the best way either. Does this make sense?
HTML
<button type="button" name='foo' value="bar1" onclick='btnChange(1, this.value); return false' class='form-control'>Button1</button>
<button type="button" name='hoo' value="bar2" onclick='btnChange(1, this.value); return false' class='form-control'>Button1</button>
JS
var button1YN = 0;
var button2YN = 0;
and so on...
var YNState;
function btnChange(tableid, btnID) {
if (btnID == "bar1") {
if (button1YN === 0) {
YNState = "yes";
button1YN = 1;
} else {
YNState = "no";
buttonY1N = 0;
}
}
if (btnID == "bar2") {
if (button2YN === 0) {
YNState = "yes";
button2YN = 1;
} else {
YNState = "no";
buttonY2N = 0;
}
}
//ajax code to update the mysql table
}
Instead of having a separate variable for each item, create a single variable to represent the state you're attempting to keep track of. This could be an object or an array, depending on your specific needs and/or preferences.
So you might have a state variable that looks like this for example:
// object mapping table names to on/off state
const state = {
tbl1: true,
tbl2: false,
tbl3: true
}
or an array:
const state = [true, false, true];
If you needed something more complex than true or false (on/off) you could use an array of objects:
const state = [
{
table: 't1',
on: true,
lastModified: '2021-03-23',
someOtherThing: 'foo'
},
{
table: 't2',
on: false,
lastModified: '2021-03-23',
someOtherThing: 'bananas'
},
]
Then you just need a function to update the state when something changes. For the simplest case, the true/false array, it could take the index of the item and the new value:
function updateItem(index, newValue) {
state[index] = newValue;
}
Or just toggle the existing true/false value at the given index:
const toggleItem = index => state[index] = !state[index];
Then just call that from your button click handler.
Here's a quick proof of concept:
// initial state. 7 buttons, all off (no value)
const buttons = Array.from({length: 7});
// function to flip the state at the given index
const toggleButton = index => {
buttons[index] = !buttons[index]; // negate existing value. true becomes false, vice-versa
update(); // redraw the dom
}
// redraws the html.
const update = () => {
const container = document.querySelector('.demo');
// just spitting out a button for each item in the array.
// the key thing here is the click handler, which you might
// want to register differently, but this works for
// demonstration purposes.
container.innerHTML = buttons.map((value, index) => `
<button onclick="toggleButton(${index})">
${value ? "✅" : "🔴"} Button ${index}
</button>
`).join('');
}
// do the initial draw
update();
/* purely cosmetic. irrelevant to functionality */
.demo {
display: flex;
flex-direction: column;
width: 100px;
}
button {
border: none;
padding: 0.5em;
border-radius: 4px;
margin: 0.5em;
}
<div class="demo" />
I'm planning on implementing Quill into my website but unfortunately the insertText function is producing the following:
TypeError: n.appendChild is not a function shadow.ts:150
wrap shadow.ts:150
formatAt shadow.ts:70
format embed.ts:26
value cursor.js:25
formatAt embed.ts:30
formatAt container.ts:98
forEachAt linked-list.ts:114
formatAt container.ts:97
formatAt block.ts:42
value block.js:78
value cursor.js:35
value selection.js:110
value quill.js:157
a quill.js:437
value quill.js:149
value toolbar.js:101
I'm extending the text blot and attempting to use the documentation notes from here (copying the divider code) but the output ends up just printing true to the editor.
JS
const Text = Quill.import("blots/text");
class SchoolNameBlot extends Text {}
SchoolNameBlot.blotName = "tagName";
SchoolNameBlot.tagName = "NAME";
const toolbarOptions = [['bold', 'italic'], ['link', 'image', 'tagName']];
Quill.register(SchoolNameBlot);
const options = {
debug: 'info',
theme: 'snow',
modules: {
toolbar: toolbarOptions
}
}
const editor = new Quill("#msgText", options);
$("#tagName-Button").click(function() {
let range = editor.getSelection(true);
editor.insertText(range.index, "insertText");
});
HTML Element:
<div class="col-md-11">
<div id="msgText"></div>
</div>
Output
From what I can tell, I am using Quill correctly so I'm not to sure why this error is being produced. I'm using the CDN's provided on their page.
I'm extending the text blot and attempting to use the documentation
notes from here (copying the divider code) but the output ends up just
printing true to the editor.
In the link presented talking about how to clone Medium, there is no blot being created that extends blots/text. Divider is created using blots/block/embed. Basically, there are 3 types of blots that can be created:
Block Blot
Inline Blot
Embed Blot
To help you better understand what I'm talking about, I suggest you to read a little about Parchment and Blots.
Now, about your problem itself... As you can see from your example, you just created a blot, but didn't add any behavior to it, and you have set your created blot tag name to NAME. Of all existing tags in HTML, there is not one with the name <NAME>. Look:
https://www.w3schools.com/TAGs/
https://techspirited.com/all-html-tags-list-of-all-html-tags
The name you give to tagName will be the HTML tag used for the result, ie what your blot will represent. If you want to add an image, for example, you need to give tagName the value IMG. For a header title, you could use h1, h2, h3, and so on.
Looking at your code, and seeing the name "tag" written on it, it seems to me that you just want to add some stylized text. Would it be? If this is your case, look at the following example:
let Inline = Quill.import('blots/inline');
class SchoolNameBlot extends Inline {
// Overriding this method, in this particular case, is what
// causes the Delta returned as content by Quill to have
// the desired information.
static formats(domNode) {
if(domNode.classList.contains('my-style')){
return true;
}
else{
return super.formats(domNode);
}
}
formats() {
// Returns the formats list this class (this format).
let formats = super.formats();
// Inline has the domNode reference, which in this
// case represents the SPAN, result defined in tagName.
formats['tag-name'] = SchoolNameBlot.formats(this.domNode);
// In the code above, it is as if we are adding this new format.
return formats;
}
}
SchoolNameBlot.blotName = 'tag-name';
SchoolNameBlot.tagName = 'span';
SchoolNameBlot.className = 'my-style';
Quill.register(SchoolNameBlot);
$(document).ready(function () {
var toolbarOptions = {
container: [['bold', 'italic'], ['link', 'image', 'tag-name']],
handlers: {
'tag-name': function(){
this.quill.insertText(0, 'Hello', 'tag-name', true);
}
}
};
var quill = new Quill('#editor', {
theme: 'snow',
modules: {
'toolbar': toolbarOptions
}
});
});
.my-style{
background: rgb(254, 255, 171);
border-radius: 2px;
padding: 2px 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<p>Instructions:</p>
<ol>
<li>Press the invisible button (with no icon) next to the add image button.</li>
</ol>
<div id="editor">
</div>
To just style text, I do not advise creating a new Blot, as there is no need for something so complex. You could use Attributors. The previous code would look as:
const Parchment = Quill.import('parchment')
var config = {
scope: Parchment.Scope.INLINE,
whitelist: ['yellow', 'red', 'blue' , 'green']
};
var SchoolName = new Parchment.Attributor.Class('my-attributor', 'style' , config);
Quill.register(SchoolName);
$(document).ready(function () {
var toolbarOptions = {
container: [['bold', 'italic'], ['link', 'image', 'my-button'] , ['clean']] ,
handlers: {
'my-button': function () {
let range = this.quill.getSelection();
this.quill.insertText(range.index, 'Hello', 'my-attributor' , 'yellow');
}
}
};
var quill = new Quill('#editor', {
theme: 'snow',
modules: {
'toolbar': toolbarOptions
}
});
});
.style-yellow{
background: rgb(254, 255, 171);
border-radius: 2px;
padding: 2px 2px;
}
.style-red{
background: rgb(255, 171, 171);
border-radius: 2px;
padding: 2px 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<p>Instructions:</p>
<ol>
<li>Press the invisible button (with no icon) next to the add image button.</li>
</ol>
<div id="editor">
</div>
As a final tip, you can always get more information from Quill official website, as well as from its repositories. For even more information, examples and frequently asked questions (Quill FAQ), take a look here.
It's it possible to get React to move an element rather than re-create it when it changes its place in the DOM?
Let's imagine I'm making a 2 pane component and I want to be able to hide/unhide one pane. Let's also imagine the panes themselves are very heavy. In my case the each pane has over 2000 elements.
In my actual code I'm using a splitter when there are 2 panes. In order to show just one pane I need to remove the splitter and replace it with a div.
The code below simulates this. If there's one pane it uses a div to contain the pane. If there's 2 panes it uses pre to contain them. In my case it would be div with 1 pain and a splitter with 2.
So, instrumenting document.createElement I see that not only are the containers created but the elements inside are recreated. In other words, in my code when go from splitter->div the 2000+ element pane will get entirely recreated which is slow.
Is there a way to tell React effectively. "Hey, don't recreate this component, just move it?"
class TwoPanes extends React.Component {
constructor(props) {
super(props);
}
render() {
const panes = this.renderPanes();
if (panes.length === 2) {
return React.createElement('pre', {className: "panes"}, panes);
} else {
return React.createElement('div', {className: "panes"}, panes);
}
}
renderPanes() {
return this.props.panes.map(pane => {
return React.createElement('div', {className: "pane"}, pane);
});
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
panes: [
"pane one",
"pane two",
],
};
}
render() {
const panes = React.createElement(TwoPanes, {panes: this.state.panes}, null);
const button = React.createElement('button', {
onClick: () => {
const panes = this.state.panes.slice();
if (panes.length === 1) {
panes.splice(0, 0, "pane one"); // insert pane 1
} else {
panes.splice(0, 1); // remove pane 1
}
this.setState({panes: panes});
},
}, "toggle pane one");
return React.createElement('div', {className: "outer"}, [panes, button]);
}
}
// wrap document.createElement so we can see if new elements are created
// vs reused
document.createElement = (function(oldFn) {
let count = 0;
let inside = false;
return function(type, ...args) {
if (!inside) { // needed because SO's console wrapper calls createElement
inside = true;
console.log(++count, "created:", type);
inside = false;
}
return oldFn.call(this, type, ...args);
}
}(document.createElement));
ReactDOM.render(
React.createElement(App, {}, null),
document.getElementById('root')
);
html { box-sizing: border-box; }
*, *:before, *:after { box-sizing: inherit; }
body { margin: 0; }
#root { width: 100vw; height: 100vh; }
.outer {
width: 100%;
height: 100%;
}
.panes {
width: 100%;
height: 100%;
display: flex;
flow-direction: row;
justify-content: space-between;
}
.pane {
flex: 1 1 auto;
border: 1px solid black;
}
button {
position: absolute;
left: 10px;
top: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I don't think there is a way to just move around DOM trees, even if there was - it will be pretty expensive, as
React's diff algorithm still needs to compare different trees and if it stumbles upon a subtree / node with different structure, it will immediately discard the old one. This is one of the assumptions that the diff algorithm makes in order to run in O(n)
Two elements of different types will produce different trees
In order to move around a cached DOM, you will first need detach it from the tree, which implies that it still needs to be reapplied later and that is a bottleneck. Inserting HTML into the DOM is very expensive, even if cached / prerendered.
My suggestion is to use CSS, because display: none / display: block is a much faster than reapplying cached DOM.
class TwoPanes extends React.Component {
render() {
return (
<div>
<Pane1 />
<Pane2 style={this.state.panes.length === 2 ? {} : {display: 'none'} } />
</div>
);
}
}
I came up with the following to modify the style of a webcomponent through:
custom properties in CSS
declaratively in the HTML tag
programatically by changing the properties
My solution uses properties that automatically modify the CSS custom properties.
It looks as follows:
<link rel="import" href="../../bower_components/polymer/polymer.html"/>
<dom-module id="my-bar">
<template>
<style>
:host {
display: block;
box-sizing: border-box;
height: var(--my-bar-bar-height, 50%);
opacity: var(--my-bar-bar-opacity, 0.8);
border: var(--my-bar-bar-border, 1px solid black);
width: 100%;
}
div {
background-color: var(--my-bar-bar-color, blue);
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
</style>
<div id="bar"></div>
</template>
<script>
Polymer({
is: 'my-bar',
properties: {
barColor: {
type: String,
observer: '_colorChanged'
},
barHeight: {
type: String,
observer: '_heightChanged'
},
barOpacity: {
type: String,
observer: '_opacityChanged'
},
barBorder: {
type: String,
observer: '_borderChanged'
}
},
_heightChanged: function () {
this._styleChanged("barHeight");
},
_colorChanged: function () {
this._styleChanged("barColor");
},
_opacityChanged: function () {
this._styleChanged("barOpacity");
},
_borderChanged: function () {
this._styleChanged("barBorder");
},
_styleChanged: function(name) {
// update the style dynamically, will be something like:
// this.customStyle['--my-bar-bar-color'] = 'red';
this.customStyle[this._getCSSPropertyName(name)] = this[name];
this.updateStyles();
},
_getCSSPropertyName: function(name) {
// retrieves the CSS custom property from the Polymer property name
var ret = "--" + this.is + "-";
var char = "";
for(i = 0; i < name.length; i++)
{
char = name.charAt(i);
if(char >= 'A' && char <= 'Z') {
ret += "-" + char.toLowerCase();
}
else {
ret += char;
}
}
return ret;
}
});
</script>
</dom-module>
Then you can either style in CSS:
my-bar {
--my-bar-bar-color: gray;
}
through HTML:
<my-bar bar-height="20%" bar-opacity="0.1" bar-border="2px solid black"></my-bar>
or JavaScript:
this.$.my-bar.barHeight = "20%;
Adding a new CSS property to the API means adding the following lines:
the property definition
the observer code to pass the property name to _styleChanged()
setting the CSS property to the CSS custom property
I don't think that in Polymer you can specify a variable or constant with the function passed to the observer, so that's why the second point is necessary.
Is there any better way to create a CSS style API for Polymer?
Any improvements or simplifications I could do?
I would recommend against doing this; it may not be compatible with future versions. Hopefully it won't be, since Polymer in many ways is a polyfill until browsers adopt/implement web components on their own.
The purpose of the custom property API is to 'approximate' the CSS variable spec. This is needed since Polymer uses a shady dom, not the real shadow dom which is not widely supported yet.
I recommend sticking to the CSS variable spec for styling.
I am trying to make an paper-card element change colors based on the status of the customers data on Fire base, but for some reason the color only updates on the second click of the customer. Right now I have the paper cards ID set to the firebase data in order to make it change colors. Here's my elements style code:
<style is="custom-style">
:host {
display: block;
}
#cards {
#apply(--layout-vertical);
#apply(--center-justified);
}
.row {
padding: 20px;
margin-left: 10px;
}
paper-card {
padding: 20px;
}
#check {
float: right;
bottom: 15px;
--paper-card
}
#Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
#Default {
/*Apply Default Style*/
/*--paper-card-content: {*/
/* background: var(--paper-red-500);*/
/*};*/
}
paper-icon-button.check{
color: var(--paper-green-500);
}
paper-icon-button.check:hover{
background: var(--paper-green-50);
border-radius: 50%;
}
#check::shadow #ripple {
color: green;
opacity: 100%;
}
.iron-selected{
color: green;
}
And here is the template:
<template>
<firebase-collection
location="https://calllistmanager.firebaseio.com/Wilson"
data="{{wilsonData}}"></firebase-collection>
<div id="cards">
<template id="cards" is="dom-repeat" items="{{wilsonData}}" as="customer">
<paper-card id="{{customer.status}}" class="{{customer.status}}" heading="[[customer.__firebaseKey__]]">
<div class="card-content">
<span>Phone: </span><span>[[customer.number]]</span>
<span>Status: </span><span>[[customer.status]]</span>
<paper-icon-button style="color: green" id="check" on-tap="checktap" icon="check">
</paper-icon-button>
</div>
</paper-card>
</template>
</div>
Here is my script:
<script>
(function() {
Polymer({
is: 'list-display',
properties: {
wilsonData: {
type: Object,
observer: '_dataObserver'
}
},
ready: function() {
var listRef = new Firebase("https://calllistmanager.firebaseio.com/Wilson");
},
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var stat;
var store = ref.child(e.model.customer.__firebaseKey__);
store.on("value", function(snapshot){
stat = snapshot.child("status").val();
});
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.customer.status = "Default";
}
else {
store.update({
"status": "Done"
});
e.model.customer.status = "Done";
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles()
}
});
})();
at first I thought the problem may be that the function runs updateStyles(); faster than firebase can update but it always works fine on the second click...any suggestions?
I think the problem could be caused by the call to firebase. store.on("value", is not a synchronous function. However, later in your code you assume that you already have a value, that will be set later on whenever the value event fires. You could try adding the rest of your code in the event handler. Like this:
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var store = ref.child(e.model.customer.__firebaseKey__);
store.once("value", function(snapshot){
var stat = snapshot.child("status").val();
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.set("customer.status", "Default");
}
else {
store.update({
"status": "Done"
});
e.model.set("customer.status", "Done");
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles();
}.bind(this));
}
Essentially, you wait until the stat variable has been set to do the rest of your tasks. Also note, the bind(this) at the end, which will allow you to update the the styles from the event handler.
Update
There are a couple of more issues. First it's better to uses classes for changing the styles and not IDs. IDs should not change. Then, to bind to the class attribute, use the $ sign. When you update the model, you should use the set API.
Have a look at this plunker. It is a small working example (only works in Chrome) that changes styles when you click the checkmark. It does not use Firebase, however.
Here's how you could to the style with classes.
.Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
And in your template:
<paper-card class$="{{customer.status}}" heading="[[customer.__firebaseKey__]]">