i'm migrating one of my older jquery plugins from DOM jungle to this fancy mvvm framework knockout.
Which technique would i use to properly display a popup container? I ahve to populate it 'by call' since i get a json feed every time.
I tried an approach using the with binding, but it still attempts to populate the partial at its first runtime.
<!-- ko with: daySubmitFormViewModel -->
<div class="ec-consulation-lightbox">
<form id="cForm" class="form-container">
// Some bindings here.
</form>
</div>
<!-- /ko with: -->
It can be done without custom binding as well. Example is below
<div class="modalWindowBackground" data-bind="visible: popupDialog" >
<div class="modalWindow" data-bind="with:popupDialog">
<div class="content">
<h2 data-bind="text: title"></h2>
<p>
<span data-bind="text: message"></span>
</p>
<div class="buttonSpace">
<input type="button" class="closeButton" data-bind="value: closeButtonText, click: $root.hidePopupDialog" />
</div>
</div>
</div>
</div>
Viewmodel code:
self.showAlert = function (title, message, closeButtonText) {
self.popupDialog({ title: title, message: message, closeButtonText: closeButtonText });
};
self.hidePopupDialog = function () {
self.popupDialog(null);
};
//Code which opens a popup
self.remove = function () {
.... some code ...
if (someCondition) {
self.showAlert('SomeTitle', 'Message', 'OK');
return;
}
.... some code ...
};
Create a custom binding, have its open / close function trigger on a observable.
I've done a custom binding for jQuery Dialog that uses this approuch in combination with KO
templates.
<div id="dialog" data-bind="dialog: { autoOpen: false, modal: true, title: dialogTitle }, template: { name: 'dialog-template', data: dialogItem, 'if': dialogItem }, openDialog: dialogItem"></div>
You can find my binding here along with some others
https://github.com/AndersMalmgren/Knockout.Bindings
Live demo http://jsfiddle.net/H8xWY/102/
https://github.com/One-com/knockout-popupTemplate
That pretty much does what you ask for. It's deeply configurable, and under steady development (we use it in our web applications ourselves).
Disclaimer: I'm a One.com developer. I am also the person who originated the above mentioned lib.
Related
New to vuejs. I am trying to write javascript directly in the vue file. Below is the code. I keep getting the following errors...
compiled with problems
70:18 error 'openpopup' is defined but never used no-unused-vars
73:18 error 'closepopup' is defined but never used no-unused-vars
Html with script:
<template>
<div class="customers-page">
<h2>Customers</h2>
<button type="add" class="add-button" onclick="openpopup()">Add</button>
<div class="popup" id="popup">
<h3>Input the following information</h3>
<button type="add-customer" class="submit-customer-button" onclick="closepopup()">Submit</button>
</div>
</div>
</template>
<script type="application/javascript" >
let popup = document.getElementById("popup");
function openpopup(){
popup.classList.add("open-popup")
}
function closepopup(){
popup.classList.remove("open-popup")
}
</script>
The very purpose to use Vue is to leverage its features for handling this type of logic reactively, Following is the snippet which can be used(vue 3 options api)
<template>
<div class="customers-page">
<h2>Customers</h2>
<button type="add" class="add-button" #click="openpopup">Add</button>
<!-- onclick="openpopup()" -->
<div class="popup" :class="popupToggle ? 'open-popup' : ''">
<h3>Input the following information</h3>
<button type="add-customer" class="submit-customer-button" #click="closepopup">Submit</button>
<!-- onclick="closepopup()" -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
popupToggle: false,
};
},
methods: {
openpopup() {
this.popupToggle = true;
},
closepopup() {
this.popupToggle = false;
},
},
};
</script>
Here the popup view is maintained by a state variable popupToggle, if you want to use something similar to id then you should go through refs here
When you use frameworks like Vue / React etc, using the native DOM is discourage (basically those .getElementById, .classlist.add or similar). One main reason is security, anyone can go to inspect and do DOM manipulations.
If you want to avoid the Options API boilerplate, you can use Composition API, which is similar to what you are doing.
Besides, if you are using conditional rendering, v-if is recommended instead of class binding, because the elements are not rendered if it is false.
<template>
<div class="customers-page">
<h2>Customers</h2>
<button type="add" class="add-button" #click="openPopup()">Add</button>
<div v-if="isShowPopup" class="popup">
<h3>Input the following information</h3>
<button type="add-customer" class="submit-customer-button" #click="closePopup()">Submit</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isShowPopup = ref(false)// acts similar to options api data block
// here I am using arrow functions
const openPopup = () => {
isShowPopup.value = true // in composition API, instead of using this., you use .value
}
const closePopup = () => {
isShowPopup.value = false
}
</script>
how can I call a function in the viewModel from within a ko.binding?
I want clicking on an editorable area to also show the selected text in a select-box.
I have also run into some other problems:
Full-text doesn´t show at until user has chosen from "select".
Editing the text in the full-text inline editor cause the outline editor to collapse.
Editing the header cause the observable to include all HTML-code for edited header.
In the binding: "tinymceInstance.remove()" returns an error message. Commenting it out however causes the desired highlighting function, but inline editor cannot be accessed.
Highlighting does not occur as wanted.
Here´s the html:
<select size="2" style="width: 170px;" data-bind="options: Textbatches, optionsCaption: 'Choose...', optionsText: 'TextbatchTitle', value: SelectedText, click: $root.showIt2Me"></select>
<div>
<fieldset>
<legend>Textbatches:<img id="btnMetatoggle" class="ui-icon ui-icon-arrow-4-diag" style="display: inline-block" /></legend>
<div id="details" class="textbatchdetails" data-bind="with: SelectedText">
<div class="editor" data-bind="tinymce: TextbatchText, tinymceOptions: { selector: 'div.editor', inline: false }"></div>
</div>
</fieldset>
</div>
<div id="full-text">
<fieldset>
<legend >Full text:</legend>
<div>
<!-- ko with: $root.SelectedText -->
<!-- ko foreach: $root.Textbatches() -->
<div data-bind="attr: {'id': 'Text' + TextbatchId}, event:{ click: $root.clickedThis }, css: { 'inverted-text': TextbatchId === $root.SelectedText().TextbatchId}">
<h2 class="editableArea" data-bind="tinymce: TextbatchTitle, tinymceOptions: { selector: 'h2.editableArea', inline: true }"></h2>
<div class="editableArea" data-bind="tinymce: TextbatchText, tinymceOptions: { selector: 'div.editableArea', inline: true }"></div>
</div>
<!-- /ko -->
<!-- /ko -->
</div>
</fieldset>
</div>
See Fiddle: http://jsfiddle.net/x8581f1y/23/
Any help highly appreciated.
Thanx in advance!
The parameters you have for your binding handlers are incorrect. You're misusing one of the parameters which is likely causing issues down the line. For both init() and update(), the parameters are:
element
valueAccessor
allBindings
viewModel
bindingContext
Since you ultimately want a reference to the view model, there's no need to use the binding context, you have a direct reference to the view model right in the fourth parameter.
function (element, valueAccessor, allBindings, viewModel, bindingContext) {
...
viewModel.someFunction(someArgs); // call directly on the view model
}
As for your other issues, I don't know much about tiny mce but you're making some assumptions that are invalid. There is no id on the elements so it will always be undefined in your handler. The id gets assigned after tiny mce is initialized on the element. But since you just need to access the reference to the instance, all you need use is the element itself, not the id.
ko.utils.domNodeDisposal.addDisposeCallback(element, function (element) {
var tinymceInstance = $(element).tinymce(); // get the instance
if (tinymceInstance) {
tinymceInstance.remove();
}
});
Using i18next, how can I translate JQuery Mobile widgets? Specifically, I'd like to know how to do that without resorting to using data-i18n-target to modify generated inner elements, because that is brittle since future widget versions may change the generated code.
Is there a specific page lifecycle event I can subscribe to in order to be able to have i18next modify the DOM before the widget transformation happens?
In this example (see jsfiddle), some markup is correctly translated, but homeBtn and submitBtn are not:
HTML:
<div id="home_page" data-role="page">
<div data-role="header">
<a id="homeBtn" href="/" data-icon="home" data-i18n>app.home</a>
<h2 data-i18n>app.title</h2>
</div>
<div data-role="content">
<div data-role="fieldcontain">
<label for="textinput1" data-i18n>app.label</label>
<input type="text" name="textinput1" id="textinput1" value=""></input>
</div>
<form action="">
<input id="submitBtn" type="submit" data-i18n="[value]app.button" />
</form>
</div>
<div data-role="footer">
<center>
<p data-i18n>app.footer</p>
</center>
</div>
</div>
JavaScript:
var i18nOpts = {
resStore: {
dev: {
translation: {
app: {
button: 'Button',
home: 'Home',
label: 'Label',
footer: 'Footer',
title: 'i18n Test'
}
}
},
}
};
i18n.init(i18nOpts).done(function() {
$("html").i18n();
});
jQuery Mobile widgets are auto-initialized (Markup enhancement) on two events, pagebeforecreate and pagecreate. The majority of widgets are initialized once pagecreate occurs.
All you need is to wrap i18n code in pagebeforecreate.
$(document).on("pagebeforecreate", function () {
// code
});
Demo
I'm having a problem using knockout and a form and getting bindings to apply without throwing errors.
I would like to split the logic for the form into several view models but I'm getting errors with bindings in bars and foos not being found when I attempt to bind foobar
I've tried to display this in the example below.
Is there a way to achieve the desired behaviour? Is there a way to say combine all the bindings from the three view models then assign them to foobar?
bars_observable is a ko.observable created in the contructor of barViewModel.
<div id="foobar">
<form data-bind="with: newFooBar, submit: submitFooBar">
<section id="bars">
<div data-bind="text: bars_observable"></div>
</section>
<section id="foos">
foo stuff
</section>
</form>
</div>
<script type="text/javascript">
$(function () {
var foobarViewModel, fooViewModel, barViewModel;
foobarViewModel = new ViewModels.FoobarViewModel({
fooViewModel: new ViewModels.FooViewModel({}),
barViewModel: new ViewModels.BarViewModel({})
});
ko.applyBindings(foobarViewModel, document.getElementById("foobar"));
});
</script>
The error would be
"Uncaught Error: Unable to parse bindings. Message: ReferenceError: bars_observable is not defined;"
I would recommend to put fooViewModel and barViewModel objects into FoobarViewModel. In this case you have to call ko.applyBindings only once.
<div id="foobar">
<form data-bind="with: newFooBar, submit: submitFooBar">
<section id="bars" data-bind="with: barViewModel">
<div data-bind="text: bars_observable"></div>
</section>
<section id="foos" data-bind="with: forViewModel">
foo stuff
</section>
</form>
</div>
<script type="text/javascript">
$(function () {
var foobarViewModel = new ViewModels.FoobarViewModel({});
ko.applyBindings(foobarViewModel, document.getElementById("foobar"));
});
function ViewModels.FoobarViewModel() {
var self = this;
self.fooViewModel = new ViewModels.FooViewModel({});
self.barViewModel = new ViewModels.BarViewModel({});
...
}
</script>
If you are writing a modular system, creating a top level view model may not be possible. In knockout 2.0 you can tell knockout to stop parsing bindings at a certain point. Then you can call applyBindings again for the container. Here is an article which explains how to do it.
http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html
I've looked at quite a few links for people trying to add content / change the template for a Dojo Dialog. This was the most promising.
However, whenever I do something like this:
Dialog declared in HTML:
<div class="djDialog" id="dgViewer" data-dojo-type="TemplatedDialog" data-dojo-props="title: 'My Dialog', draggable:false"></div>
Dialog Template:
<div class="dijitDialog" role="dialog" aria-labelledby="${id}_title">
<div data-dojo-attach-point="titleBar" class="dijitDialogTitleBar">
<span data-dojo-attach-point="titleNode" class="dijitDialogTitle" id="${id}_title"></span>
<span data-dojo-attach-point="closeButtonNode" class="dijitDialogCloseIcon" data-dojo-attach-event="ondijitclick: onCancel" title="${buttonCancel}" role="button" tabIndex="-1">
<span data-dojo-attach-point="closeText" class="closeText" title="${buttonCancel}">x</span>
</span>
</div>
<!-- containerNode from original Dialog template -->
<div data-dojo-attach-point="containerNode" class="dijitDialogPaneContent">
<!-- All "custom" content -->
<div data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design:'headline'" style="height:300px">
<div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'center'">
</div>
<div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'top'">
<button data-dojo-type="dijit.form.Button">Edit</button>
<button data-dojo-type="dijit.form.Button">Status</button>
</div>
</div>
<!-- End "custom" content -->
</div>
</div>
Custom Dialog class:
define([
'dojo/_base/declare',
'dijit/Dialog',
'dijit/_TemplatedMixin',
'dojo/text!./dialog_templates/View.html'],
function(
declare,
Dialog,
_Mixin,
_template){
return declare('TemplatedDialog', [Dialog, _Mixin], {
templateString : _template,
constructor : function(){
}
})
})
In my console (using Chrome) I just see:
<div data-dojo-attach-point="containerNode" class="dijitDialogPaneContent" style="width: auto; height: auto; "></div>
... and empty node where content should be.
So far I haven't found anyone who seems to have successfully extended dijit.Dialog in terms of templates. Is this possible?
Edit
After trying some variations on this template:
<div class="dijitDialog" role="dialog" aria-labelledby="${id}_title">
<div data-dojo-attach-point="titleBar" class="dijitDialogTitleBar">
<span data-dojo-attach-point="titleNode" class="dijitDialogTitle" id="${id}_title"></span>
<span data-dojo-attach-point="closeButtonNode" class="dijitDialogCloseIcon" data-dojo-attach-event="ondijitclick: onCancel" title="${buttonCancel}" role="button" tabIndex="-1">
<span data-dojo-attach-point="closeText" class="closeText" title="${buttonCancel}">x</span>
</span>
</div>
<!-- containerNode from original Dialog template -->
<div class="dijitDialogPaneContent">
<div data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design:'headline'" style="height:100%;width:100%">
<div data-dojo-attach-point="containerNode" data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'center'"></div>
</div>
<div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'bottom'">
<button data-dojo-type="dijit.form.Button">Edit</button>
</div>
</div>
</div>
The error Uncaught TypeError: Cannot read property '0' of undefined is being thrown. This is the stack, if it helps. I'm using the uncompressed version from the Google CDN to help in debugging.
_childElements dojo.js.uncompressed.js:8341
getStepQueryFunc dojo.js.uncompressed.js:8597
query dojo.js.uncompressed.js:9005
query dojo.js.uncompressed.js:5248
_2._checkIfSingleChild _ContentPaneResizeMixin.js:2
_4._size Dialog.js:2
_4.show Dialog.js:2
Removing the data-dojo-type and -props from the containerNode resolves this, but it's still not getting things closer to having a working custom templated Dialog.
Reason for troubles with doing templating on a contentpane is, that whatever template-contents you put into the domNode referenced with the attach-point 'containerNode', you will loose on startup.
If there is no 'href' nor 'content' attributes set, they will simply be set to an empty string, thus leaving the Dialog.containerNode.innerHTML == ""
You have no need for deriving from _TemplatedMixin as the Dialog itself is a templated widget. Instead change this to _WidgetsInTemplateMixin to compensate for the BorderContainer layout widgets and your dijit.form contents. Also, your markup within the custom template should be pre-required, so you could go with something like this here:
Change the template from old attachpoint for container to this
<div data-dojo-attach-point="containerNode"
data-dojo-type="dijit.layout.ContentPane"
data-dojo-props="region:'center'">
Then add requirements to your markup widgets in the template plus the _WidgetsInTemplateMixin:
define(["dijit/Dialog",
"dijit/_WidgetsInTemplateMixin",
"dojo/text!./dialog_templates/View.html",
// rest are for rendering example
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/form/Button"
}. ... );
Result should ressemble this, keeping your template View.html change in mind:
define([
'dojo/_base/declare',
'dijit/Dialog',
"dijit/_WidgetsInTemplateMixin",
"dojo/text!./dialog_templates/View.html",
// rest are for rendering example
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/form/Button"],
function(
declare,
Dialog,
_Mixin,
_template){
return declare('TemplatedDialog', [Dialog, _Mixin /*careful, widgetsintemplate is tricky*/ ], {
templateString : _template
})
})
You can fiddle here
EDIT:
As there is troubles with dialogs containing borderlayouts (its not unheard of anyways) here's a workaround:
_checkIfSingleChild: function() {
delete this._singleChild;
domClass.toggle(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild);
},
templateString: '....'
Im not certain of the consequences, im thinking the borderlayout of yours might start to misbehave if you try to programmatically change its contents and dimensions.. But it will render - at least it does here: updated fiddle