Ext.draw: how to control sprites? - javascript

I'm trying to make a drawing application with ExtJS4, the MVC-way.
For this, I want to make several widgets. This is my code for a widget:
VIEW:
Ext.define('VP.view.fields.Field' ,{
extend: 'Ext.draw.Sprite',
alias: 'widget.field',
constructor: function() {
var parentConfig = {
id: 'field',
fill: '#FFFF00',
type: 'rect',
width: '100%',
height: '100%',
x: 0,
y: 0
};
this.callParent([parentConfig]);
}
});
CONTROLLER:
Ext.define('VP.controller.Fields', {
extend: 'Ext.app.Controller',
views: [
'fields.Field'
],
init: function() {
console.log('Initialized Field controller!');
var me = this;
me.control({
'field': { //trying to control the widget.field, this fails
//'viewport > draw[surface]': { //this controls the surface successfully
//'viewport > draw[surface] > field': { //fails
click: this.addObject
}
});
},
addObject : function(event,el){
console.log(el);
//console.log(drawComponent);
}
});
How can I control this custom sprite? Can only Ext.components be controlled by a controller? (Ext.draw.Sprite doesn't extend Ext.component).

In addition to my previous answer, you could add the listener on the sprite and fire an event on some object. Then you could catch it in the controller.
Example:
spriteObj....{
listeners: {
click: {
var canvas = Ext.ComponentQuery.query('draw[id=something]')[0];
var sprite = this;
canvas.fireEvent('spriteclick',sprite);
}
}
}
In the controller:
init: function() {
console.log('Initialized Field controller!');
var me = this;
me.control({
'draw[id=something]': {
spriteclick: this.doSomething
}
});
},
doSomething: function (sprite) {
console.log(sprite);
}

I've been messing around with the same thing. It appears that you can't do it because it is an svg object that Ext just 'catches' and adds a few events to it. Because it doesn't have an actual alias (xtype) because it is not a component, it cannot be queried either. You have to do the listeners config on the sprite unfortunately.
Also, I tried doing the method like you have of extending the sprite. I couldn't get that to work, I assume because it is not a component.
I was able to keep this in the all in the controller because I draw the sprites in the controller. So, I can manually define the listeners in there and still use all the controller refs and such. Here's an example of one of my drawing functions:
drawPath: function(canvas,points,color,opacity,stroke,strokeWidth, listeners) {
// given an array of [x,y] coords relative to canvas axis, draws a path.
// only supports straight lines
if (typeof listeners == 'undefined' || listeners == "") {
listeners = null;
}
if (stroke == '' || strokeWidth == '' || typeof stroke == 'undefined' || typeof strokeWidth == 'undefined') {
stroke = null;
strokeWidth = null;
}
var path = path+"M"+points[0][0]+" "+points[0][1]+" "; // start svg path parameters with given array
for (i=1;i<points.length;i++) {
path = path+"L"+points[i][0]+" "+points[i][1]+" ";
}
path = path + "Z"; // end svg path params
var sprite = canvas.surface.add({
type: 'path',
opacity: opacity,
fill:color,
path: path,
stroke:stroke,
'stroke-width': strokeWidth,
listeners: listeners
}).show(true);
this.currFill = sprite;
}
You can see where I just pass in the parameter for the listeners and I can define the object elsewhere. You could do this in your own function or wherever in the controller. While the actual sprite should be in the view, this keeps them a little more dynamic and allows you to manipulate them a little easier.

Related

Aframe Dynamic Canvas as Texture

I was trying to use a canvas as texture in my aframe project. I found some instructions here. It mentioned:
The texture will automatically refresh itself as the canvas changes.
However, I gave it a try today and the canvas could only be changed / updated in init function. Afterwards the update to canvas cannot be reflected. Here is my implementation:
module.exports = {
'canvas_component': {
schema: {
canvasId: { type: 'string' }
},
init: function () {
this.canvas = document.getElementById(this.data.canvasId);
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = "#FF0000";
this.ctx.fillRect(20, 20, 150, 100);
setTimeout(() => {
this.ctx.fillStyle = "#FFFF00";
this.ctx.fillRect(20, 20, 150, 100);
}, 2000);
}
}
The color change of of the texture was never changed. Is there anything I missed? Thank you so much for any advice.
I could never get it to work with those instructions (never checked out if bug or improper use though), but you can achieve the same with Three.js:
// assuming this is inside an aframe component
init: function() {
// we'll update this manually
this.texture = null
let canvas = document.getElementById("source-canvas");
// wait until the element is ready
this.el.addEventListener('loaded', e => {
// create the texture
this.texture = new THREE.CanvasTexture(canvas);
// get the references neccesary to swap the texture
let mesh = this.el.getObject3D('mesh')
mesh.material.map = this.texture
// if there was a map before, you should dispose it
})
},
tick: function() {
// if the texture is created - update it
if (this.texture) this.texture.needsUpdate = true
}
Check it out in this glitch.
Instead using the tick function, you could update the texture whenever you get any callback from changing the canvas (mouse events, source change).
The docs are out of date, I've made a pull request to update them. Here is the code that shows how to do it now:
src: https://github.com/aframevr/aframe/issues/4878
which points to: https://github.com/aframevr/aframe/blob/b164623dfa0d2548158f4b7da06157497cd4ea29/examples/test/canvas-texture/components/canvas-updater.js
We can quickly turn that into a component like this, for example:
/* global AFRAME */
AFRAME.registerComponent('live-canvas', {
dependencies: ['geometry', 'material'],
schema: {
src: { type: "string", default: "#id"}
},
init() {
if (!document.querySelector(this.data.src)) {
console.error("no such canvas")
return
}
this.el.setAttribute('material',{src:this.data.src})
},
tick() {
var el = this.el;
var material;
material = el.getObject3D('mesh').material;
if (!material.map) {
console.error("no material map")
this.el.removeAttribute('live-canvas')
return;
}
material.map.needsUpdate = true;
}
});
(remember to declate your components before your scene...)
usage:
<body>
<canvas id="example-canvas"></canvas>
<a-scene>
<a-box live-canvas="src:#example-canvas;"></a-box>
</a-scene>
</body>
live glitch code demo here:
https://glitch.com/edit/#!/live-canvas-demo?path=index.html%3A58%3A43
You can of course be more efficient than a tick handler if you just intentionally run the equivalent code manually whenever you update the canvas yourself, if that makes more sense / isn't happening frame-by-frame.

Trigger mouseover event on a Raphael map

I am using the Raphael plugin for the first time and so far I managed to create a map of Germany and to outline all different regions inside. I found out how to bind mouse-over effects, so when I hover over a region its color changes. Everything looks fine until the moment when I want to trigger the same mouse-over effect from outside the map. There is a list of links to all the regions and each link should color its respective geographical position (path) on the map when hovered. The problem is that I don't know how to trigger the mouse-over effect from outside.
This is the reference guide I used for my code: Clickable France regions map
This is my map initialization:
var regions = [];
var style = {
fill: "#f2f2f2",
stroke: "#aaaaaa",
"stroke-width": 1,
"stroke-linejoin": "round",
cursor: "pointer",
class: "svgclass"
};
var animationSpeed = 500;
var hoverStyle = {
fill: "#dddddd"
}
var map = Raphael("svggroup", "100%", "auto");
map.setViewBox(0, 0, 585.5141, 792.66785, true);
// declaration of all regions (states)
....
var bayern = map.path("M266.49486,..,483.2201999999994Z");
bayern.attr(style).data({ 'href': '/bayern', 'id': 'bayern', 'name': 'Bayern' }).node.setAttribute('data-id', 'bayern');
regions.push(bayern);
This is where my "normal" mouse effects take place:
for (var regionName in regions) {
(function (region) {
region[0].addEventListener("mouseover", function () {
if (region.data('href')) {
region.animate(hoverStyle, animationSpeed);
}
}, true);
region[0].addEventListener("mouseout", function () {
if (region.data('href')) {
region.animate(style, animationSpeed);
}
}, true);
region[0].addEventListener("click", function () {
var url = region.data('href');
if (url){
location.href = url;
}
}, true);
})(regions[regionName]);
}
I have a menu with links and I want to bind each of them to the respective region, so that I can apply the animations, too.
$("ul.menu__navigation li a").on("mouseenter", function (e) {
// this function displays my pop-ups
showLandName($(this).data("id"));
// $(this).data("id") returns the correct ID of the region
});
I would appreciate any ideas! Thanks in advance!
I figured out a way to do it. It surely isn't the most optimal one, especially in terms of performance, but at least it gave me the desired output. I would still value a better answer, but as of right now a new loop inside the mouse-over event does the job.
$("ul.menu__navigation li a").on("mouseenter", function (e) {
// this function displays my pop-ups
showLandName($(this).data("id"));
// animate only the one hovered on the link list
var test = '/' + $(this).data("id");
for (var regionName in regions) {
(function (region) {
if (region.data('href') === test) {
region.animate(hoverStyle, animationSpeed);
}
})(regions[regionName]);
}
});

JS Assigning Callbacks through Iteration

I have a small program that uses RaphaelJS to accept some nodes and edges, much like arborjs and sigmajs, to produce tree graphs. I would use either of the said libraries but I am trying to learn some JS and graph theory and I figured why not try something like that on my own.
The nodes are iterated through and placed onto the paper, and a callback function is given for hover over and hover out. The animation part of the hover over hover out works however the popup text shows the last value that was apart of the iteration when it was assigned.
I have found that this is because I need to use closures, however I have attempted to write a closure following several different examples that I have found.
I have posted an example on JSFiddle: link
var jgraph = {
obj_nodes : [],
obj_edges : [],
addNodes : function(obj) {
this.obj_nodes[obj.id] = obj;
},
getNodes : function() {
return this.obj_nodes;
},
addEdges : function(obj) {
this.obj_edges[obj.id] = obj;
},
getEdges : function() {
return this.obj_edges;
},
createGraph : function(paper, obj) {
var point_x, point_y,
source_x, source_y, target_x, target_y, popup;
for (var i = 0; i < obj.nodes.length; i++) {
this.obj_nodes[obj.nodes[i].id] = obj.nodes[i],
point_x = this.obj_nodes[obj.nodes[i].id].x,
point_y = this.obj_nodes[obj.nodes[i].id].y;
popup = jgraph.createPopup(paper, obj.nodes[i].id);
paper.circle(point_x, point_y, 10).attr({
fill : "#e74c3c",
stroke : "none"
}).hover(function() {
this.animate({
fill : "#3498db",
transform : "s1.5"
}, 900, "elastic");
popup.show().toFront();
}, function() {
this.animate({
fill : "#e74c3c",
transform : "s1"
}, 900, "elastic");
popup.hide();
});
}
for (var i = 0; i < obj.edges.length; i++) {
this.obj_edges[obj.edges[i].id] = obj.edges[i];
source_x = this.obj_nodes[obj.edges[i].source].x,
source_y = this.obj_nodes[obj.edges[i].source].y,
target_x = this.obj_nodes[obj.edges[i].target].x,
target_y = this.obj_nodes[obj.edges[i].target].y;
p.path("M " + source_x + " " + source_y + " L " + target_x + " " + target_y).toBack().attr({
stroke : "#e74c3c",
"stroke-width" : 4
});
}
},
createPopup : function(paper, id) {
return paper.text(this.obj_nodes[id].x, this.obj_nodes[id].y - 20, this.obj_nodes[id].label).hide().attr({
"font-size" : "13px"
});
}
};
Thanks in advance!
Closures can certainly be maddening. In this case, the problem is that you're installing your hover handlers in the context of the for loop, and the closure ties the handler's reference to popup to the variable defined outside of the context of the for loop. Naturally, by the time the hover handlers fire, the for loop has finished executing and the value of popup reflects the last label the loop handled.
Try this instead -- you're basically just declaring an anonymous function inside your for loop and passing each value of popup into that function, which makes the closure bind to the values of specific calls to that anonymous function:
popup = jgraph.createPopup(paper, obj.nodes[i].id);
( function( popup )
{
paper.circle(point_x, point_y, 10).attr({
fill : "#e74c3c",
stroke : "none"
}).hover(function() {
this.animate({
fill : "#3498db",
transform : "s1.5"
}, 900, "elastic");
popup.show().toFront();
}, function() {
this.animate({
fill : "#e74c3c",
transform : "s1"
}, 900, "elastic");
popup.hide();
});
} )( popup );
If I were you, I would probably assign the label to each element using Raphael elements' data method, and then create and destroy the text elements each time a hover is started and ended respectively. That way you don't need to worry about managing many invisible elements at all =)
Here's a fork of your fiddle in working condition.

Extjs dynamically switch tabs position in tabpanel

I have tabpanel:
{
xtype: 'tabpanel',
tabPosition: 'top', // it's default value
items: [/*tabs*/]
}
And some button which changes layout:
{
xtype: 'button',
text: 'Change layout',
handler: function (btn) {
var layout = App.helper.Registry.get('layout');
if (layout === this.getCurrentLayout()) {
return;
}
if (layout === 'horizontal') {
newContainer = this.down('container[cls~=split-horizontal]');//hbox laout
oldContainer = this.down('container[cls~=split-vertical]');//vbox layout
tabPanel.tabPosition = 'top';
} else {
newContainer = this.down('container[cls~=split-vertical]');
oldContainer = this.down('container[cls~=split-horizontal]');
tabPanel.tabPosition = 'bottom';
}
oldContainer.remove(somePanel, false);
oldContainer.remove(tabPanel, false);
newContainer.insert(0, somePanel);
newContainer.insert(2, tabPanel);
newContainer.show();
oldContainer.hide();
}
When I change layout, me also need change the position of the tabs.
Of course changing config property tabPosition has no effect.
How i can switch tabPosition dynamically?
I'm afraid in case of tabpanel the only way is to destroy the current panel and recreate it from config object with altered tabPosition setting. You can use cloneConfig() method to get config object from existing panel.

How can I represent a 2d grid of pages using Backbone.js?

I'm rather new to Backbone, so I'm looking for some architecture advice for a new project.
I have a 2d grid of pages (like map tiles), and I'd like to display and navigate through them. I'm using backbone in other areas of the site, and I'm thinking it would help here too?
Example: (Image at the bottom)
A user is on Page1. They click a link on the right of the page. I load that page from my webserver, into a new element out of view on the right, and 'slide' it into place.
(My end aim is to load all surrounding pages in the background, so that when a user clicks the transition is immediate. Hence I wanted to store it in some kind of Backbone model setup)
(I am aware that there are plenty of slide presentation libraries around, but that isn't what I'm trying to achieve, so I need something that I can customise from the ground up)
Thanks :)
Demonstration
I wrote a little demonstration of a 2d grid system with Backbone.JS: http://www.atinux.fr/demos/2d-grid/
It doesn't have improvements like pre-render images...
Explications
It's rather simply, I just have one collection and one view.
Each picture is a model and its coordinates are in the Id (example:{ id: '0x5' }, here x = 0 and y = 5.). All the models are stocked in the collection of the view.
The view bind the arrows, and then the user click on it :
I change the actual coordinates
I get the model on the collection with the new coordinates
I change the actual background with a transition.
Data
The data of the models is an array of hashs:
[
{
id: '0x0',
url: 'assets/images/pics/rice.jpg'
}, {
id: '0x1',
url: 'assets/images/pics/beach.jpg'
}, {
id: '1x0',
url: 'assets/images/pics/cold.jpg'
}
]
Code
HTML of the view :
<div id="grid">
<div class="bg-trans"></div>
<div class="bg"></div>
<a class="arrow top"></a>
<a class="arrow left"></a>
<a class="arrow right"></a>
<a class="arrow bottom"></a>
</div>
Grid View:
Backbone.View.extend({
initialize: function () {
// Coordinates of the actual picture
this.x = 0;
this.y = 0;
// Show actual picture (actual model, position 0x0)
this.$('.bg, .bg-trans').css('background-image', "url('"+this.model.attributes.url+"')");
// Display available arrows
this.showArrows();
},
// bind arrows
events: {
'click .left': 'moveLeft',
'click .right': 'moveRight',
'click .top': 'moveTop',
'click .bottom': 'moveBottom'
},
// Return the actual coordinates by defaults (without parameters)
coord: function (x, y) {
x = (x == null ? this.x : x);
y = (y == null ? this.y : y);
return x + 'x' + y;
},
// Show available arrows
showArrows: function () {
// jQuery here, no important for the answer
// [...]
},
// When use click on left arrow
moveLeft: function () {
var newCoord = this.coord(this.x - 1),
model = this.collection.get(newCoord);
if (model) {
this.x--;
this.model = model;
// ANIMATION
// [...]
/////////////////////
this.showArrows();
}
},
moveRight: function () {
var newCoord = this.coord(this.x + 1),
model = this.collection.get(newCoord);
if (model) {
this.x++;
this.model = model;
// ANIMATION
// [...]
/////////////////////
this.showArrows();
}
},
moveTop: function () {
console.log(this.y - 1);
var newCoord = this.coord(null, this.y - 1),
model = this.collection.get(newCoord);
if (model) {
this.y--;
this.model = model;
// ANIMATION
// [...]
/////////////////////
this.showArrows();
}
},
moveBottom: function () {
var newCoord = this.coord(null, this.y + 1),
model = this.collection.get(newCoord);
if (model) {
this.y++;
this.model = model;
// ANIMATION
// [...]
/////////////////////
this.showArrows();
}
}
})
At any time, you can access at the actual model display on the grid with gridView.model because we define this.model when we change the coordinates.
All the code
Of course you can download all the code here (.zip): http://www.atinux.fr/demos/2d-grid.zip

Categories

Resources