I would like to add a tooltip to the items in a Dojo Select. This code adds a tooltip when the store is contained in the script.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#import "https://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css";
#import "https://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/resources/dojo.css";
</style>
<script src="https://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js" type="text/javascript" data-dojo-config="async: true"></script>
<script>
require(["dijit/form/Select",
"dojo/store/Memory",
"dojo/domReady!"
], function (Select, Memory) {
var store = new Memory({
data: [
{ id: "foo", label: '<div tooltip="Foo Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">FOO</div>' },
{ id: "bar", label: '<div tooltip="Bar Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">Bar</div>' }
]
});
var s = new Select({
store: store,
labelType: 'html',
labelAttr: 'label'
}, "target");
s.startup();
});
function showTooltip(el) {
dijit.showTooltip(el.getAttribute('tooltip'), el);
}
function hideTooltip(el) {
dijit.hideTooltip(el);
}
</script>
</head>
<body class="claro">
<div id="target"></div>
</body>
</html>
However, in my application, my store is in a separate module (stores.js).
define([], function () {
return {
priority: [
{ id: "foo", label: '<div tooltip="Foo Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">FOO</div>' },
{ id: "bar", label: '<div tooltip="Bar Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">Bar</div>' }
]
};
};
I set the module in the require ("modules/stores") and put the alias in the function (Stores) and create my select using this code.
new Select({
id: "cboPriority",
store: new Memory({ data: Stores.priority }),
labelType: 'html',
labelAttr: 'label'
}, "divPriority").startup();
I've tried adding the showTooltip and hideTooltip functions in the module, but I still get the console error "ReferenceError: showTooltip is not defined". What is the proper way of setting up the script and the module so I can show the tooltip?
You're attempting to set up inline onmouseover event handlers on elements via your label strings. This is going to attempt to call a global showTooltip function, and no such function exists - your showTooltip function is enclosed within your require factory function.
Given that you are creating an HTML label with a node containing an attribute indicating the text to display, a better option in this specific case would be to use dojo/on's event delegation to hook up a single event handler for mouseover and another for mouseout:
var dropdownNode = s.dropDown.domNode;
on(dropdownNode, '[data-tooltip]:mouseover', function () {
Tooltip.show(this.getAttribute('data-tooltip'), this);
});
on(dropdownNode, '[data-tooltip]:mouseout', function () {
Tooltip.hide(this);
});
(Tooltip in the above code refers to the dijit/Tooltip module, and I elected to use a data-attribute which would at least be valid HTML5.)
To be quite honest, I'd prefer avoiding embedding HTML in data to begin with, but this is likely the shortest path from where you are to where you want to be.
Related
I'm struggling to get a Dijit dialog to work for a reproducible example. I took the working code from this JSfiddle and simply tried to turn this into a named function to use throughout the example.
The author uses:
new Button({label: 'Show dialog', onClick: function() {
//Create dialog programmatically here
}
});
but I've changed this to be slightly different:
function launchSelectDialog(selectOptions) {
//Create dialog programmatically here
}
registry.byId("default-launch", "onClick", launchSelectDialog(allOpts));
Here is my version. Unfortunately, this just launches the dialog immediately upon loading the page, and never again when clicking on the button.
I have checked the NoWrap option in JSFiddle. I have no other clues about what's going on.
Please help if you have any ideas.
There are couple of issue.
1) Like others a have pointed out, you are invoking the function not setting up the event with function. hence the dialog is visible onload.
2) You need to wait till the html has been parse. or you need to use parser.parse()
Here is the updated fiddler: http://jsfiddle.net/49y3rxzg/9/
() is an invocation operator. You are calling the function yourself and the returned value of the function is set as the event handler. If you want to reuse the function, use a closure:
function launchSelectDialog(selectOptions) {
// the returned function will be used as the event handler
return function() {
// the passed `selectOptions` is remembered in this context
}
}
Another option is:
registry.byId("default-launch", "onClick", function() {
launchSelectDialog(allOpts);
});
You need to initiate your Button widget before retrieving with registry.byId().
In your code actually registry.byId("default-launch") was returning undefined;
Also registry.byId() function accept only an id so additional parameters will be ignored.
To fix it you should initiate a Button instance properly and declare launchSelectDialog(allOpts) withinonClick, as:
var myButton = new Button({
label: "Default Options",
onClick: function() {
launchSelectDialog(allOpts);
}
}, "default-launch");
Below fixed version for your script.
http://jsfiddle.net/usm829jq/
require([
"dojo/dom",
"dijit/Dialog",
"dijit/form/Button",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/registry",
"dojo/domReady!"
], function(dom, DijitDialog, Button, BorderContainer, ContentPane, registry) {
var allOpts = [{
label: "Foo",
value: "foo"
}, {
label: "Bar",
value: "bar"
}]
var myButton = new Button({
label: "Default Options",
onClick: function() {
launchSelectDialog(allOpts);
}
}, "default-launch");
function launchSelectDialog(SelectOptions) {
var layout = new BorderContainer({
design: "headline",
style: "width: 400px; height: 400px; background-color: yellow;"
});
var centerPane = new ContentPane({
region: "center",
style: "background-color: green;",
content: "center"
});
var actionPane = new ContentPane({
region: "bottom",
style: "background-color: blue;"
});
(new Button({
label: "OK"
})).placeAt(actionPane.containerNode);
(new Button({
label: "Cancel"
})).placeAt(actionPane.containerNode);
layout.addChild(centerPane);
layout.addChild(actionPane);
layout.startup();
var dialog = new DijitDialog({
title: 'dialog title',
style: {
//width: '400px',
//height: '400px',
},
content: layout
});
dialog.containerNode.style.backgroundColor = "red";
dialog.startup();
dialog.show();
}
})
I want to add custom tool in kendo editor.
It is pretty good in jQuery method via directive, But have problem with model binding.
It seems I have to use angular method like this:
<textarea naccordion id="htmleditor" ng-model="Model._Active.Paragraph" class="editor" k-options="accordion" k-encoded="false" kendo-editor k-tools="['fontName','bold','italic','underline','strikethrough','fontSize','justifyLeft','justifyCenter','justifyRight','justifyFull','foreColor','insertUnorderedList','insertOrderedList','indent','outdent','createLink','unlink','insertImage','cleanFormatting','backColor','viewHtml','formatting']"></textarea>
So, I put my k-options code in controller:
$scope.accordion = {
tools:
{
name: "accordion",
tooltip: "Accordion items",
exec: function (e) {
var editor = $(this).data("kendoEditor");
editor.exec("inserthtml", {
value: "<accordion close-others='true'><accordion-group is-open='Model._openSettings'><accordion-heading>[Title]</accordion-heading><br>[Text]</accordion-group></accordion>"
});
}
}
};
It is not work.
I do not know what is accordion scope is right or no?
Any idea?
tools needs to be an array:
$scope.accordion = {
tools:[
{
name: "accordion",
tooltip: "Accordion items",
exec: function (e) {
var editor = $(this).data("kendoEditor");
editor.exec("inserthtml", {
value: "<accordion close-others='true'><accordion-group is-open='Model._openSettings'><accordion-heading>[Title]</accordion-heading><br>[Text]</accordion-group></accordion>"
});
}
}
]
};
I have these two custom Polymer Elements (Polymer 1.0.3):
Displays text to be translated.
Displays button to trigger loading of translation.
I also have a Behavior that holds the translations (json object) and contains all the functions that make translation possible.
This is what I expect to happen:
Click the button in Element 2
Translations load into the Behavior
Language selection is set in the Behavior
Text in Element 1 is updated with the translated equivalent
Steps 1 - 3 happen, but 4 doesn't. The text is never updated. I can get it to work if Elements 1 & 2 are combined as the same element, but not if they're separate (any they need to be separate).
If you're wondering about the "kick" property, it's something I learned from Polymer 0.5. It got things working when the two elements were combined, so I'm thinking it'll be be necessary when the elements are separate.
Any idea how I can make this happen? I'm open to alternative paradigms.
Code
This is roughly how my code is laid out. I also made a plunker with a single-page test case.
index.html
<!doctype html>
<html>
<head>
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<link rel="import" href="behavior.html">
<link rel="import" href="element1.html">
<link rel="import" href="element2.html">
</head>
<body>
<my-element></my-element>
<another-element></another-element>
</body>
</html>
Element 1
<dom-module id="my-element">
<template>
<p>{{localize(label, kick)}}</p>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element',
behaviors: [
behavior
],
properties: {
label: {
type: String,
value: 'original'
}
}
});
</script>
Element 2
<dom-module id="another-element">
<template>
<button on-click="buttonClicked">load</button>
</template>
</dom-module>
<script>
Polymer({
is: 'another-element',
behaviors: [
behavior
],
buttonClicked: function() {
this.registerTranslation('en', {
original: 'changed'
})
this.selectLanguage('en');
}
});
</script>
Behavior
<script>
var behavior = {
properties: {
kick: {
type: Number,
value: 0
},
language: {
type: String,
value: 'fun'
},
translations: {
type: Object,
value: function() {
return {};
}
}
},
localize: function(key, i) {
if (this.translations[this.language] && this.translations[this.language][key]) {
return this.translations[this.language][key];
}
return key;
},
registerTranslation: function(translationKey, translationSet) {
this.translations[translationKey] = translationSet;
},
selectLanguage: function(newLanguage) {
this.language = newLanguage;
this.set('kick', this.kick + 1);
}
};
</script>
First, although the notion is to have behavior be a conduit for shared data between instances, as written, each instance will have it's own copy of the translations object and the kick property.
Second, even if that data was privatized so it could be shared, the kick binding made via localize(label, kick) is in a different scope from the expression that modifies kick (i.e. this.set('kick', this.kick + 1); [{sic} this could simply be this.kick++;]).
To notify N instances of a change in shared data, one must keep track of those instances. A good way to do this is by attaching event listeners. Another way is simply keeping a list.
Here is an example implementation of your design:
<script>
(function() {
var instances = [];
var translationDb = {};
var language = '';
window.behavior = {
properties: {
l10n: {
value: 0
}
},
attached: function() {
instances.push(this);
},
detached: function() {
this.arrayDelete(instances, this);
},
_broadcast: function() {
instances.forEach(function(i) {
i.l10n++;
});
},
localize: function(key, i) {
if (translationDb[language] && translationDb[language][key]) {
return translationDb[language][key];
}
return key;
},
registerTranslation: function(translationKey, translationSet) {
translationDb[translationKey] = translationSet;
},
selectLanguage: function(newLanguage) {
language = newLanguage;
this._broadcast();
}
};
})();
</script>
<dom-module id="my-element">
<template>
<p>{{localize(label, l10n)}}</p>
</template>
<script>
Polymer({
behaviors: [
behavior
],
properties: {
label: {
type: String,
value: 'original'
}
}
});
</script>
</dom-module>
<dom-module id="another-element">
<template>
<button on-tap="buttonClicked">load</button>
</template>
<script>
Polymer({
behaviors: [
behavior
],
buttonClicked: function() {
this.registerTranslation('en', {
original: 'changed'
});
this.selectLanguage('en');
}
});
</script>
</dom-module>
In my pursuit to get a binding for an associative array to work, I've made significant progress, but am still blocked by one particular problem.
I do not understand how to create a binding from strictly javascript
Here is a jsFiddle that shows more details than I have posted here:
jsFiddle
Basically, I want to do a new binding within the shown $.each function that would be equivalent to this...
<div data-template="display-associative-many" data-bind="repeat: Root.Items"></div>
Gets turned into this ...
<div data-template="display-associative-single" data-bind="source: Root['Items']['One']"></div>
<div data-template="display-associative-single" data-bind="source: Root['Items']['Two']"></div>
<div data-template="display-associative-single" data-bind="source: Root['Items']['Three']"></div>
And I am using the repeat binding to create that.
Since I cannot bind to an associative array, I just want to use a binding to write all of the bindings to the objects in it.
We start again with an associative array.
var input = {
"One" : { Name: "One", Id: "id/one" },
"Two" : { Name: "Two", Id: "id/two" },
"Three" : { Name: "Three", Id: "id/three" }
};
Now, we create a viewModel that will contain that associative array.
var viewModel = kendo.observable({
Name: "View Model",
Root: {
Items: input
}
});
kendo.bind('#example', viewModel);
Alarmingly, finding the items to bind was pretty easy, here is my binding so far;
$(function(){
kendo.data.binders.repeat = kendo.data.Binder.extend({
init: function(element, bindings, options) {
// detailed more in the jsFiddle
$.each(source, function (idx, elem) {
if (elem instanceof kendo.data.ObservableObject) {
// !---- THIS IS WHERE I AM HAVING TROUBLE -----! //
// we want to get a kendo template
var template = {};// ...... this would be $('#individual-item')
var result = {}; // perhaps the result of a template?
// now I need to basically "bind" "elem", which is
// basically source[key], as if it were a normal HTML binding
$(element).append(result); // "result" should be a binding, basically
}
});
// detailed more in the jsFiddle
},
refresh: function() {
// detailed more in the jsFiddle
},
change: function() {
// detailed more in the jsFiddle
}
});
});
I realize that I could just write out the HTML, but that would not perform the actual "binding" for kendo to track it.
I'm not really sure what you are attempting to do, but it seemed to me that the custom "repeat" binding was unnecessary. Here's what I came up with. Is this on track with what you are trying to do?
Here is a working jsFiddle example.
HTML
<div id="example">
<div data-template="display-associative-many" data-bind="source: Root.Items"></div>
</div>
<script type="text/x-kendo-template" id="display-associative-many">
#for (var prop in data) {#
# if (data.hasOwnProperty(prop)) {#
# if (data[prop].Id) {#
<div><span>${data[prop].Id}</span> : <span>${data[prop].Name}</span></div>
# }#
# }#
#}#
</script>
JavaScript
$(function () {
var input = {
"One" : { Name: "One", Id: "id/one" },
"Two" : { Name: "Two", Id: "id/two" },
"Three" : { Name: "Three", Id: "id/three" }
};
var viewModel = new kendo.data.ObservableObject({
Id: "test/id",
Root: {
Items: input
}
});
kendo.bind('#example', viewModel);
});
I got a custom Ext.Component with a view XTemplates. I do need some of theese Templates outside of the view in my controller too.
Is it possible to refer to static members in functions of a XTemplate. Or is there another much better way???
something like this:
Ext.define('app.view.ApplicationHeader', {
extend: 'Ext.Component',
name: 'app-header',
xtype: 'app-header',
height: 67,
margin: 0,
statics: {
mainIconTpl: new Ext.XTemplate('someTemplate'),
navigationItemsTpl: new Ext.XTemplate( 'anotherTemplate'),
userInfoTpl: new Ext.XTemplate('userTemplate')
},
html: new Ext.XTemplate('... {[ this.renderMainIcons() ]} {[ this.renderUserInfo() ]} ...',
'... {[ this.renderNavigationBarItems() ]} ...',
{
me: this,
renderMainIcons: function () {
return view.static.mainIconTpl.apply(MR.Sitemap.Items);
},
renderUserInfo: function () {
return view.static.userInfoTpl.apply();
},
renderNavigationBarItems: function () {
return view.static.navigationItemsTpl.apply();
}
}).apply()
});
i also dont know how i could apply subtemplates which are members of the view. I declared them global right know which i really dont like to do.
please!
Your code is not working because the apply method of the main template is called before the class definition (i.e. the define method) is even called.
You can create your static template that uses the other static members of the class in the post-create function (see the last param of the define method).
Then in order for the template to be available, I would override the initComponent method and set the html property there.
Ext.define('app.view.ApplicationHeader', {
extend: 'Ext.Component',
name: 'app-header',
xtype: 'app-header',
height: 67,
margin: 0,
statics: {
mainIconTpl: new Ext.XTemplate('someTemplate'),
navigationItemsTpl: new Ext.XTemplate('anotherTemplate'),
userInfoTpl: new Ext.XTemplate('userTemplate')
},
initComponent: function() {
// Here, your statics are available, and you're in the scope of your
// class *instance*
this.html = this.self.viewTemplate.apply();
this.callParent(arguments);
}
}, function() {
// In the post create function, this is the class constructor
// (i.e. app.view.ApplicationHeader)
var cls = this;
// In fact, you could also create your sub templates here if you prefer
// e.g.
// cls.useInfoTpl = new Ext.XTemplate('userTemplate')
// So, viewTemplate will be a static property of the class
cls.viewTemplate = new Ext.XTemplate('... {[ this.renderMainIcons() ]} {[ this.renderUserInfo() ]} ...',
'... {[ this.renderNavigationBarItems() ]} ...', {
renderMainIcons: function() {
return cls.mainIconTpl.apply();
},
renderUserInfo: function() {
return cls.userInfoTpl.apply();
},
renderNavigationBarItems: function() {
return cls.navigationItemsTpl.apply();
}
});
});
According to the link, you should be able to put this directly in your XTemplate. No need for statics
{[ MyApp.tpls.someOtherTpl.apply(values) ]}
Multiple templates in Nested List
You could also try putting all of these XTemplates in initComponent instead since you're not injecting any values for XTemplate after initial component render. The apply() will just return you an HTML fragment which should be able to be appended anywhere within the XTemplate.
If you're trying to put logical or conditional tpl operators i.e. <tpl for="parent.someVar">...</tpl> in any of the sub XTemplates, then that's another problem so it all depends on what you're trying to accomplish.
Ext.define('app.view.ApplicationHeader', {
extend: 'Ext.Component',
name: 'app-header',
xtype: 'app-header',
height: 67,
margin: 0,
initComponent: function() {
var me = this,
me.mainIconTpl = new Ext.XTemplate('someTemplate'),
me.navigationItemsTpl = new Ext.XTemplate( 'anotherTemplate'),
me.userInfoTpl = new Ext.XTemplate('userTemplate');
me.tpl = new Ext.XTemplate(
'...', me.mainIconTpl.apply(MR.Sitemap.Items),
'...', me.navigationItemsTpl.apply(someValues),
'...', me.userinfoTpl.apply(someValues),
'...'
);
Ext.apply(me, {
html: me.tpl
});
me.callParent();
}
});