Mithril show component on button click - javascript

I have to developp an Single Application Page, i choose Mithril.
I need to render a component on button click, this is my code :
var accountView = {
controller: function (data) {
return {
showOtherPage: function () {
??? how to render an other component ?
}
}
},
view: function (ctrl) {
return [
m("button", { onclick: ctrl.showOtherPage }, "Account")
];
}
}
Thanks in advance

If you're using Mithril's routing functionality and want to show a whole new page, then you can use a link rather than using a button. (Personally, this is how I normally anticipate handling these scenarios.) eg,
m("a[href=/otherPage]", { config: m.route }, "Account")
Fiddle: https://jsfiddle.net/11qjx341/
(Alternatively, you could also call m.route('/otherPage') within the showOtherPage function if a link is not appropriate for some reason.)
Or, if you're not using Mithril's routing (eg if you're using m.mount), but still want to render a whole new page, you might want to call m.mount with the new view to have it rendered. eg
m.mount(document.body, otherView);
Fiddle: https://jsfiddle.net/91g9db6n/
As a third option, if your new view is actually meant to coexist with the current page, you can have a component that's shown or hidden based on state. eg
return [
m("button", { onclick: ctrl.showModal.bind(ctrl, !showModal) }, showModal ? "Hide Account" : "Account")
, showModal ? m.component(OtherView) : null
];
Fiddle: https://jsfiddle.net/mk27tfq1/

Related

Spaces are not recognized correctly in the TipTap Editor

we use the rich text editor of TipTap in our project.
But we have the problem, that spaces are not recognized correctly and only after every 2 click a space is created. As framework we use Vue.JS.
import { Editor, EditorContent, EditorMenuBar } from 'tiptap'
import {
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
Bold,
Italic,
History
} from 'tiptap-extensions'
import EditorMenuButton from './EditorMenuButton.vue'
export default {
name: 'editor',
components: {
EditorMenuButton,
EditorMenuBar,
EditorContent
},
props: {
value: {
type: null,
default: ' '
}
},
data () {
return {
innerValue: ' ',
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new BulletList(),
new OrderedList(),
new ListItem(),
new Bold(),
new Italic(),
new History()
],
content: `${this.innerValue}`,
onUpdate: ({ getHTML }) => {
this.innerValue = getHTML()
}
})
}
},
watch: {
// Handles internal model changes.
innerValue (newVal) {
this.$emit('input', newVal)
},
// Handles external model changes.
value (newVal) {
this.innerValue = newVal
this.editor.setContent(this.innerValue)
}
},
mounted () {
if (this.value) {
this.innerValue = this.value
this.editor.setContent(this.innerValue)
}
},
beforeDestroy () {
this.editor.destroy()
}
}
</script>
does anyone have any idea what could be the reason for assuming only every two spaces?
We had the same problem, we kept the onUpdate trigger but changed the watch so that it would only invoke editor.setContent when the value was actually different.
watch: {
value() {
let html = this.editor.getHTML();
if (html !== this.value) {
this.editor.setContent(this.value);
}
},
},
"Okay the problem is that the watcher will get fired when you type in the editor. So this will check if the editor has focus an will only update the editor content if that's not the case."
watch: {
value(val) {
if (!this.editor.focused) {
this.editor.setContent(val, false);
}
}
},
issue: https://github.com/ueberdosis/tiptap/issues/776#issuecomment-667077233
This bug for me was caused by doing something like this:
watch: {
value: {
immediate: true,
handler(newValue) {
this.editor.setContent(newValue)
},
},
},
Removed this entirely and the bug went away. Maybe this will help someone in future.
Remove onUpdate section and the bug will disapear. I don't know why, but it's interesting to know how to reproduce the bug.
That does help. Following this advice, I am currently using the onBlur event instead of onUpdate, while obtaining the content's HTML using the editor instance and the getHTML() function, as such: this.editor.getHTML().
(In my case I $emit this value in order for it to be reactive to my parent component, but that may be irrelevant for the original question).
Maybe you should try this.
watch: {
// Handles external model changes.
value (newVal) {
// convert whitespace into \u00a0 ->
let content = newVal.replace(/\s/g, "\u00a0");
this.editor.setContent(content)
}
},
It seems like the normal white space has been removed by html automatically. Therefore, I convert whitespace into 'nbsp;' and it's worked.
The code you provided seems to be working just fine. So the issue most likely is produced by a side effect in either your code or some dependency.
To debug this issue you could look for event listeners, especially regarding key press or key down events and looking if you are checking for space key specifically somewhere (event.keyCode === 32 or event.key === " "). In conjunction with event.preventDefault this could explain such an issue.
Another more broad way to debug this is to strip away parts from your code until the bug disappears or add to a minimal example until the bug appears.
Remove onUpdate section and the bug will disapear. I don't know why, but it's interessing to know how to reproduce the bug.
However if you create a "minimal reproductible example" the bug does not appear.
So what ? I don't know.
I found a workaround which is to use vuex.
Rather than assign the value returned by getHTML() in the innerValue variable and then issue an 'input' event, I put this value in the store.

Change Electron's menu item's status dynamically

Like in any standard native application, also my electron's application needs to change the status (enabled/dsabled) of several menu item, based on live usage results.
I am setting up my menu in main.js:
function createWindow () {
...
...
require('./menu/mainmenu');
}
The MenuItem I need to change is defined in mainmenu:
{ label: "Show Colors",
accelerator: 'CmdOrCtrl+1',
enabled: getStatus(),
click() {getWebviewWebContents().send('switchToColors');}
},
where getStatus() is function returning false or true.
All this is not working in Electron, as the menu is created at application start and it can't be modified at all. I believe this is a serious lack, as dynamic menu items are very common (i.e.: menu checkboxes, enabled/disabled, etc).
Is there any workaround for this?
I have fixed this by setting an Id to the menu item,
{ label: "Show Colors",
id: 'color-scale',
accelerator: 'CmdOrCtrl+1',
enabled: getStatus(),
click() {getWebviewWebContents().send('switchToColors');}
},
and getting the menu item with:
myItem = menu.getMenuItemById('color-scale')
Then, when I need to enable/disable it programmatically, I am using:
myItem.enabled = true
or
myItem.enabled = false
The only workaround so far I aware and using is reconstruct whole menu each time menuitem changes. This is not very ergonomics friendly, but works suffeciently enough and doesn't cause lot of overhead.
Here is my solution:
main.js (main process)
const menuTemplate = [{
label: 'Options',
submenu: [
{
label: 'Config',
enabled: false,
click() { //do stuff }
}
]
}];
// Enable menu items when user login. Fetching value from renderer process
ipcMain.on('logged-in', (event, args) => {
if (args !== true) {
return;
}
// Modify menu item status
menuTemplate[0].submenu[0].enabled = true;
// Rebuild menu
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
});

rendering vue.js components and passing in data

I'm having trouble figuring out how to render a parent component, display a list of contracts in a list on part of the page, and when a user clicks on one of them, display the details of that specific contract on the other part of the page.
Here is my slim file:
#contracts_area
.filter-section
ul
li.filter-item v-for="contract in contractsAry" :key="contract.id" #click="showContract(contract)"
| {{ contract.name }}
.display-section
component :is="currentView" transition="fade" transition-mode="out-in"
script type="text/x-template" id="manage-contracts-template"
div
h1 Blank when page is newly loaded for now
script type="text/x-template" id="view-contract-template"
div :apply_contract="showContract"
h1#display-item__name v-name="name"
javascript:
Vue.component('manage-template', {
template: '#manage-contracts-template'
});
Vue.component('view-contract', {
template: '#view-contract-template',
props: ['show_contract'],
data: function() {
return {
name: ''
}
},
methods: {
showContract: function(contract) {
return this.name = contract.name
}
}
});
Vue.http.headers.common['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content');
var contractsResource = Vue.resource('/all_contracts{/id}.json');
var contracts = new Vue({
el: '#contracts_area',
data: {
currentView: 'manage-template',
contractsAry: [],
errors: {}
},
mounted: function() {
var that = this;
contractsResource.get().then(
function(res) {
that.contractsAry = res.data;
}
)
},
methods: {
showContract: function(contract) {
this.currentView = 'view-contract'
}
}
});
Basically I'd like it so that when a user clicks on any contract item in the .filter-section, it shows the data for that contract in the .display-section. How can I achieve this?
In short you can bind a value to a prop.
.display-section
component :is="currentView" :contract="currentContract"
view-contract
props: ['contract']
contracts-area
data: {
currentContract: null,
},
methods: {
showContract: function(contract) {
this.currentView = "view-contract";
this.currentContract = contract;
}
}
There are multiple ways to pass data in Vue.
Binding values to props.
Using ref to directly call a method from a child component.
Custom Events. Note that to pass events globally, you will need a global event bus.
A single central source of truth (i.e. vuex)
I have illustrated methods 1, 2, 3 in Codepen
Note that 2nd and 3rd methods will only work after your component has been rendered. In your case, since your components for currentView are dynamic and when user clicked, display-section component does not yet exists; it will not receive any events yet. So their content will be empty at first.
To workaround this you can directly access $parent in mounted() from child component, however this would create coupling between them. Another solution is creating the components but conditionally displaying them. And one another solution would be waiting until child component has been mounted and then emitting events.
If your needs are simple I suggest binding values to props (1), else you may consider using something like vuex.

show/hide panel in ExtJS

I have a panel in user-interface called Code, I dont want to display that panel to specific users when they log in based on their roles. I am new to ExtJS. I have the algorithm/condition to block user's , but I am unsure where to apply it in this code. The .js file is:
analysisCodePanel = new Ext.Panel( {
id : 'analysisCodePanel',
title : 'Code',
region : 'center',
split : true,
height : 90,
layout : 'fit',
listeners :
{
activate : function( p ) {
GLOBAL.IDs[1] = null;
GLOBAL.IDs[2] = null;
p.body.mask("Loading...", 'mask-loading');
runAll(Data, p);}
return;
},
deactivate: function(){
},
collapsible : true
});
My condition is check whether user is Admin so I can do GLOBAL.IsCodeAdmin() then show the above panel else hide it from the user logged in.
if this panel is a child of viewport then you have to use your controller to show and hide the panel.
In your controller put listener for viewport rendering like below. Make sure your read docs and getting started carefully. Then I'll understand how to control elements using different events. This link is a good start http://docs.sencha.com/extjs/4.2.1/#!/guide/getting_started
// ExtJs controller
Ext.define('app.controller.ViewPortController', {
extend: 'Ext.app.Controller',
refs: [
{
ref: 'myPanel', // this elemenet can be referred as getMyPanel()
selector: 'panel[id=analysisCodePanel]' // selector to get panel reference
}
],
init: function () {
this.control({
'viewport': {
'render': this.viewPortRender // on viewport render this function will be called
}
})
},
viewPortRender: function () {
if (GLOBAL.IsCodeAdmin()) {
this.getMyPanel().show(); // show panel
} else {
this.getMyPanel().hide(); // hide panel
}
}
}
);
I solved the problem by using an attribute for panel called disabled and setting it to true.

Backbone.js router/views logic

I am writing my first Backbone.js application and I am having some trouble figuring out the best way to program it. I have 2 main views:
Shows an index of all my models.
Shows a specific model for editing.
But #2 has many different 'modules' like I can edit the 'news' section, or 'about' section etc...
All these modules are in a navigation bar.
That navigation bar is hidden when I am displaying view # 1 (index of all models). It is visible in view # 2(a specific model) in order to navigate between different modules.
I have routes setup like this:
routes: {
'', 'index',
'communities': 'index',
'communities/:id': 'main',
'communities/:id/news', 'news',
'communities/:id/about', 'about'
},
So my question is, when 'news' or 'about' action is called, do I add a navigation bar in each method? Isn't that redundant? I am going to have like 8-10 different modules, add navigation bar each time seems very repetitive. Is there a better way?
The only time I want the navigation bar to be hidden is when showing index.
I came across this same problem when I created my first somewhat complex Backbone app. Along with your concern of redundant code, I was concerned about events bound to my navbar that may not get unbound as the navigation bar changed. To solve the problem, I wound up creating a view hierarchy, with one manager view managing the navigation bar a whole, and separate views for each type of navigation menu I wanted to display, which would be passed to the manager view to render to the page.
Here's an example of my implementation.
Before we start, here is a close function I added to Backbone's View prototype which unbinds events and removes the view
Backbone.View.prototype.close = function() {
if(this.beforeClose) { this.beforeClose(); }
this.remove();
this.unbind();
}
First, here is my Manager View. Its render function closes whatever menu is currently displayed and replaces it with the one passed to it as view. While slightly redundant, I created an explicit empty function to make my router code easier to understand.
var App.Views.SubNavBar = Backbone.View.extend({
currentView: null,
el: '#subnav-wrap',
render: function(view) {
if(this.currentView) { this.currentView.close(); }
this.currentView = view;
this.$el.html(view.el);
},
empty: function() {
if(this.currentView) { this.currentView.close(); }
this.currentView = null;
}
});
Second, here is a base view that all of my specific navigation menu views extend. Since they will all have the same tagName, className, id, and initialize and render functions, this keeps repetition to a minimum
var App.Views.SubNavBase = Backbone.View.extend({
tagName: 'ul',
className: 'nav nav-pills',
id: 'subnav',
template: _.template($('#tmpl-subnav').html(),
initialize: function() {
if(this.setLinks) { this.setLinks(); }
this.render();
},
render: function() {
this.$el.html(this.template({links:this.links}));
return this;
}
});
Here is an example of a view for a specific navigation menu. You can see that all I need to do is define the links I want to appear in the menu. When I instantiate this view, the functions of SubNavBase will handle populating the view with the required HTML. Note that I also have some events attached to this view.
var App.Views.Projects.DisplayNav = App.Views.SubNavBase.extend({
setLinks: function() {
this.links = {
'Edit Project': {
icon: 'edit',
class: 'menu-edit',
href: '#projects/'+this.model.get('id')+'/edit'
},
'Add Group': {
icon: 'plus',
class: 'menu-add-group',
href: '#projects/'+this.model.get('id')+'/groups/new'
},
'Delete Project': {
icon: 'trash',
class: 'menu-delete',
href: '#'
}
}
},
events: {
'click a.menu-delete' : 'delete'
},
delete: function(e) {
e.preventDefault();
// here goes my code to delete a project model
}
});
Now, here is the underscore.js template I use to turn the links object above into a list of <li> elements. Note that I use <# instead of <% for my templates since this is a rails app and rails already uses <%
<script type="text/template" id="tmpl-subnav">
<# _.each(links,function(link, title) { #>
<li>
<a href="<#= link.href #>" class="<#= link.class #>">
<i class="icon-<#= link.icon #>"></i>
<#= link.title #>
</a>
</li>
<# }); #>
</script>
Finally, to put it all together, here is an example Router function that creates and renders the nav menu. The steps that occur are as follows:
App.Views.Projects.DisplayNav gets passed a model and populates its this.el with the corresponding HTML, as determined by the underscore.js template
App.SubNavBar has its render function called with the new menu view
App.SubNavBar checks to see if there is currently another menu in the navigation bar; if so, it calls its view's close() function
App.SubNavBar finally appends the passed view's HTML to itself, maintaining a reference to the view for later use
I've included only the relevant parts of the router code
var App.Routers.Projects = Backbone.Router.extend({
routes: {
'projects/:id' : 'display'
},
display: function(id) {
var p = projects.get(id);
var subnav = new App.Views.Projects.DisplayNav({model:p})
App.SubNavManager.render(subnav); // App.SubNavManager is an instance of App.Views.SubNavBar
}
});
The benefit to all of this is that I can now attach events to my menu-specific views, and the manager view will take care of unbinding them if the user navigates to different content and the menu changes.
Of course, there are many other patterns you can use to handle navigation menus, but hopefully this will help you on the path.
Try this:
routes: {
'', 'index',
'communities': 'index',
'communities/:id': 'main',
'communities/:id/:section': 'openSection'
},
openSection : function(id, section){
if( section ){
this.addNavigationBar();
}
switch( section ){
case 'news' :
this.news();
break;
case 'about' :
this.about();
break;
default:
this.main();
}
}
If your url contents a section you will add the navigation bar and then call you normal method as you have.

Categories

Resources