First of all I would like to say I have very few experience in Django and plotly, so excuse me if I may be asking something which makes no sense or has very few meaning.
In Django enviroment I know that programming logic takes place in the "Views.py" file, and all variables set on this file can be transfered to the "template.html" file and use them inside double curly braces "{{ VariableFromView }}".
I was wondering if there is any way, once the plot is created, to retrieve the limits of the displayed area in the plotly plot, either on the "view.py" or "template.py" files, and save them in a variable which could be used in the "template.html" file, such as:
Variable definition example:
xbrush = [plotly.xmin, plotly.xmax]
ybrush = [plotly.ymin, plotly.ymax]
Variable call in template
{{ xbrush }}
{{ ybrush }}
In my case I amb using Python code for the "Views.py" and Javascript and html for the "templates.html" file (as well as a little bit of Django template language for dealing with variables)
Thank you very much for your help!
There are multiple ways to achieve the same. I would suggest you to go through django tutorial once again to get better understanding of template context and variables. Since you already have variables xbrush and ybrush in your views.py, just make sure you return these in the context of your response. Like render('template.html', {'xbrush':xbrush, 'ybrush': ybrush}).Now in your template.html you can access these values using {{ xbrush }}. So now all you need now is create a script tag in your html file and now you can assign these values to javascript variables like var xbrush = {{ xbrush }}
You'll need to do this using JavaScript, since rendering and all interaction with a Plotly plot is done in the browser. There's a full example (including a CodePen) that's close to what you're trying to do in the Plotly documentation here: https://plot.ly/javascript/zoom-events/
var plot = document.getElementById('plot').contentWindow;
var plotTwo = document.getElementById('plotTwo').contentWindow;
document.getElementById('plot').onload = function() {
pinger = setInterval(function(){
plot.postMessage({task: 'ping'}, 'https://plot.ly')
}, 100);
};
window.addEventListener('message', function(e) {
var message = e.data;
if(message.pong) {
console.log('Initial pong, frame is ready to receive');
clearInterval(pinger);
// Listening for zoom events, but you can also listen
// for click and hover by adding them to the array
plot.postMessage( {
task: 'listen',
events: ['zoom']
}, 'https://plot.ly');
plotTwo.postMessage({
task: 'relayout',
'update': {
'xaxis.fixedrange': true,
'yaxis.fixedrange': true
},
}, 'https://plot.ly');
}
else if( message.type == 'zoom' ){
console.log('zoom', message);
drawRectangle( message['ranges'] );
}
else {
console.log(message);
}
});
function drawRectangle( ranges ){
var rect = {
'type': 'rect',
'x0': ranges['x'][0],
'y0': ranges['y'][0],
'x1': ranges['x'][1],
'y1': ranges['y'][1],
'fillcolor': 'rgba(128, 0, 128, 0.7)',
}
plotTwo.postMessage({
'task': 'relayout',
'update': { shapes: [rect] },
}, 'https://plot.ly');
}
function newPlot(){
var plotURL = document.getElementById('plotURL').value + '.embed';
var iframe = document.getElementById('plot');
iframe.src = plotURL;
var iframeTwo = document.getElementById('plotTwo');
iframeTwo.src = plotURL;
}
Related
I try to write HTML widget with JavaScript code in the thinger.io Dashboard. The data from the "thing" can be used in the HTML by inserting the next code {{value}} into some HTML tag body.
But, I cannot use it in the JavaScript block.
Pure HTML widget:
For example use in the HTML widget:
<h1>Millis data</h1>
<p>Millis value is </p><b>{{value}}</b>
The result of this
Widget with JavaScript block (don't work as needed):
I tried to use the same data for plotting (Plotly example).
I can use in the HTML tag id=data, but, I don't know how to use this data in the JavaScript block.
My try:
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="data" value={{value}}>Data - {{value}}</div>
<div id="tester" style="width:900px;height:300px;"></div>
<script>
TESTER = document.getElementById('tester');
Plotly.newPlot( TESTER, [{
x: [1, 2, 3, 4, 5],
y: [1, 2, 4, 8, 16] }], {
margin: { t: 0 } } );
</script>
And resulting widget:
- The data on the plot are hard coded for example only.
I need help with integrating the ```{{value}}`` data into a JavaScript code in the thinger.io HTML widget.
There doesn't appear to be documented way to get the value in the HTML widget.
Instead it is possible to use a MutationObserver to listen to changes to the data div. This can create the Plotly plot when there is an update to the div. (You can also use jQuery to handle the change but this approach uses fewer dependencies.)
<div id="data" value={{value}}>Data - {{value}}</div>
<div id="tester" style="width:900px;height:300px;"></div>
<script>
var createPlot = function(dataEl) {
let value = dataEl.getAttribute('value');
console.log(`creating plot with: ${value}`);
if (value) {
TESTER = document.getElementById('tester');
Plotly.newPlot( TESTER, [{
x: [1, 2, 3, 4, 5],
y: JSON.parse(value) }], {
margin: { t: 0 } }
);
}
}
var dataEl = document.getElementById('data');
var script = document.createElement('script');
script.onload = function () {
createPlot(dataEl);
};
script.src = "https://cdn.plot.ly/plotly-latest.min.js";
document.head.appendChild(script);
var observer = new MutationObserver(function(mutations) {
// create plot only if Plotly has loaded
if (Plotly) {
createPlot(dataEl);
}
});
// setup the observer
var target = document.querySelector('#data');
var config = { attributes: true, attributeFilter: [ 'value'] };
observer.observe(target, config);
</script>
The plotly script may be loaded after the div is updated. So the trigger to create the plot could be either the script loading or the div mutating. In either case both plotly needs to be loaded and the div needs to be updated.
The value attribute is also a string so it needs to be converted from a string using JSON.parse().
Testing
To test this, I created an HTTP device and added a property with the string value [20,41,63,83,103]:
The widget was configured using this property:
And the result:
I would like to use a javascript loop to create multiple HTML wrapper elements and insert JSON response API data into some of the elements (image, title, url, etc...).
Is this something I need to go line-by-line with?
<a class="scoreboard-video-outer-link" href="">
<div class="scoreboard-video--wrapper">
<div class="scoreboard-video--thumbnail">
<img src="http://via.placeholder.com/350x150">
</div>
<div class="scoreboard-video--info">
<div class="scoreboard-video--title">Pelicans # Bulls Postgame: E'Twaun Moore 10-8-17</div>
</div>
</div>
</a>
What I am trying:
var link = document.createElement('a');
document.getElementsByTagName("a")[0].setAttribute("class", "scoreboard-video-outer-link");
document.getElementsByTagName("a")[0].setAttribute("url", "google.com");
mainWrapper.appendChild(link);
var videoWrapper= document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video-outer-link");
link.appendChild(videoWrapper);
var videoThumbnailWrapper = document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video--thumbnail");
videoWrapper.appendChild(videoThumbnailWrapper);
var videoImage = document.createElement('img');
document.getElementsByTagName("img")[0].setAttribute("src", "url-of-image-from-api");
videoThumbnailWrapper.appendChild(videoImage);
Then I basically repeat that process for all nested HTML elements.
Create A-tag
Create class and href attributes for A-tag
Append class name and url to attributes
Append A-tag to main wrapper
Create DIV
Create class attributes for DIV
Append DIV to newly appended A-tag
I'd greatly appreciate it if you could enlighten me on the best way to do what I'm trying to explain here? Seems like it would get very messy.
Here's my answer. It's notated. In order to see the effects in the snippet you'll have to go into your developers console to either inspect the wrapper element or look at your developers console log.
We basically create some helper methods to easily create elements and append them to the DOM - it's really not as hard as it seems. This should also leave you in an easy place to append JSON retrieved Objects as properties to your elements!
Here's a Basic Version to give you the gist of what's happening and how to use it
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com" });
let div = create("div", { id: "myDiv" });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com", textContent: "this text is a Link in the div" });
let div = create("div", { id: "myDiv", textContent: "this text is in the div! " });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
div {
border: 3px solid black;
padding: 5px;
}
<div id="mainWrapper"></div>
Here is how to do specifically what you asked with more thoroughly notated code.
//get main wrapper
let mainWrapper = document.getElementById("mainWrapper");
//make a function to easily create elements
//function takes a tagName and an optional object for property values
//using Object.assign we can make tailored elements quickly.
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//document.appendChild is great except
//it doesn't offer easy stackability
//The reason for this is that it always returns the appended child element
//we create a function that appends from Parent to Child
//and returns the compiled element(The Parent).
//Since we are ALWAYS returning the parent(regardles of if the child is specified)
//we can recursively call this function to great effect
//(you'll see this further down)
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//these are the elements you wanted to append
//notice how easy it is to make them!
//FYI when adding classes directly to an HTMLElement
//the property to assign a value to is className -- NOT class
//this is a common mistake, so no big deal!
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
//here's where the recursion comes in:
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//keep in mind that it might be easiest to read the ac functions backwards
//the logic is this:
//Append videoImage to videoThumbnailWrapper
//Append (videoImage+videoThumbnailWrapper) to videoWrapper
//Append (videoWrapper+videoImage+videoThumbnailWrapper) to link
//Append (link+videoWrapper+videoImage+videoThumbnailWrapper) to mainWrapper
let mainWrapper = document.getElementById('mainWrapper');
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//pretty fancy.
//This is just to show the output in the log,
//feel free to just open up the developer console and look at the mainWrapper element.
console.dir(mainWrapper);
<div id="mainWrapper"></div>
Short version
Markup.js's loops.
Long version
You will find many solutions that work for this problem. But that may not be the point. The point is: is it right? And you may using the wrong tool for the problem.
I've worked with code that did similar things. I did not write it, but I had to work with it. You'll find that code like that quickly becomes very difficult to manage. You may think: "Oh, but I know what it's supposed to do. Once it's done, I won't change it."
Code falls into two categories:
Code you stop using and you therefore don't need to change.
Code you keep using and therefore that you will need to change.
So, "does it work?" is not the right question. There are many questions, but some of them are: "Will I be able to maintain this? Is it easy to read? If I change one part, does it only change the part I need to change or does it also change something else I don't mean to change?"
What I'm getting at here is that you should use a templating library. There are many for JavaScript.
In general, you should use a whole JavaScript application framework. There are three main ones nowadays:
ReactJS
Vue.js
Angular 2
For the sake of honesty, note I don't follow my own advice and still use Angular. (The original, not Angular 2.) But this is a steep learning curve. There are a lot of libraries that also include templating abilities.
But you've obviously got a whole project already set up and you want to just plug in a template into existing JavaScript code. You probably want a template language that does its thing and stays out of the way. When I started, I wanted that too. I used Markup.js . It's small, it's simple and it does what you want in this post.
https://github.com/adammark/Markup.js/
It's a first step. I think its loops feature are what you need. Start with that and work your way to a full framework in time.
Take a look at this - [underscore._template]
It is very tiny, and useful in this situation.
(https://www.npmjs.com/package/underscore.template).
const targetElement = document.querySelector('#target')
// Define your template
const template = UnderscoreTemplate(
'<a class="<%- link.className %>" href="<%- link.url %>">\
<div class="<%- wrapper.className %>">\
<div class="<%- thumbnail.className %>">\
<img src="<%- thumbnail.image %>">\
</div>\
<div class="<%- info.className %>">\
<div class="<%- info.title.className %>"><%- info.title.text %></div>\
</div>\
</div>\
</a>');
// Define values for template
const obj = {
link: {
className: 'scoreboard-video-outer-link',
url: '#someurl'
},
wrapper: {
className: 'scoreboard-video--wrapper'
},
thumbnail: {
className: 'scoreboard-video--thumbnail',
image: 'http://via.placeholder.com/350x150'
},
info: {
className: 'scoreboard-video--info',
title: {
className: 'scoreboard-video--title',
text: 'Pelicans # Bulls Postgame: E`Twaun Moore 10-8-17'
}
}
};
// Build template, and set innerHTML to output element.
targetElement.innerHTML = template(obj)
// And of course you can go into forEach loop here like
const arr = [obj, obj, obj]; // Create array from our object
arr.forEach(item => targetElement.innerHTML += template(item))
<script src="https://unpkg.com/underscore.template#0.1.7/dist/underscore.template.js"></script>
<div id="target">qq</div>
I'm trying to work with this library (RPG.js) because it looks very powerfull, I looked at the official tutorial but i don't understand a lot of things, State 4 : "Initialize the canvas in your JS file :", whitch one ?
Is any one already used this library or know an other powerfull ?
Thanks for all.
Well, actually it was not so simple; I struggled I bit until I got a minimal functional game. What I did was not the "optimal" approach, but at least worked for me (I was using Mozilla Firefox in a Windows 7 OS). If someone knows how to improve what I did, I would be happy to know (since I'm a newbie in this library too), but since I was not able to find a good a simple starter tutorial in the net, I want to share what I did, in the hope I can help someone.
Before anything, create a HTML file, and call the libs inside the <head> tag:
<script src="canvasengine-X.Y.Z.all.min.js"></script>
<script src="rpgjs-X.Y.Z.min.js"></script>
And the canvas inside the <body> tag:
<canvas id="canvas_id" width="640" height="480"></canvas>
There's a 'sample' folder inside the github project (see https://github.com/RSamaium/RPG-JS), and inside this folder there is a functional game, and you can open it in the browser by the file quick.html. The idea is that if the sample game can work, then you can make your game work too, so I started trying to use the same graphics, data etc. of the sample.
First, you need to create in the root of your project the following folders and subfolders:
core
core\scene
Data
Data\Maps
Graphics
To have a minimal functional game, you need 1) a map to walk with your character, and 2) a character to control. So, you need to have graphics for some tiles to build your map and the walking model for your character. Create the subfolders below:
Graphics\Characters
Graphics\Tilesets
Graphics\Windowskins
And then copy the following files from the corresponding sample subfolder to these folders you created:
Graphics\Characters\event1.png
Graphics\Tilesets\tileset.png
Graphics\Windowskins\window.png #this file will be necessary for some of the js files below, even if not used; to remove this necessity, you'll need to edit these files - not what we want to do in the start.
In the 'core/scene' folder, you'll have some js files to load scenes (like the scene of your walking in the map, the gameover scene). I needed to copy all the js files inside the corresponding folder in the 'sample' project to the 'core/scene' folder of your game. This folder is not inside the 'sample' folder, but in the root of the github project. I only got my game working when I added all the 7 js files (without some work inside the codes you can remove the scenes you don't want, but since what we want is just to be able to run a simple game, let's copy them for now):
Scene_Gameover.js
Scene_Generated.js
Scene_Load.js
Scene_Map.js
Scene_Menu.js
Scene_Title.js
Scene_Window.js
Now, I added the following code inside a <script> tag on the html. This is more or less the same that was in the documentation. These codes will create an "actor" (a character), a "map", and the tiles you'll use in the map
For more details on maps or tiles, you should read the documentation (the same you linked in your question).
RPGJS.Materials = {
"characters": {
"1": "event1.png"
},
"tilesets": {
"1": "tileset.png"
}
};
RPGJS.Database = {
"actors": {
"1": {
"graphic": "1"
}
},
"tilesets": {
"1": {
"graphic": "1"
}
},
"map_infos": {
"1": {
"tileset_id": "1"
}
}
};
RPGJS.defines({
canvas: "canvas_id",
autoload: false
}).ready(function() {
RPGJS.Player.init({
actor: 1,
start: {x: 10, y: 10, id: 1} // Here, map id doesn't exist
});
RPGJS.Scene.map();
Lastly, create the json file setting every tile of your map. I just copied the MAP-1.json file from the 'sample' folder to inside the Data\Maps folder.
Then you'll be able to walk with your character in an empty map! Open the html file in your browser and try it!
Printscreen from the game running in a browser
Of course to have a real game you'll need to change a lot of this (as creating a database.json file and a materials.json file where you'll put most of the code you put in the <script> tag), but with the basics I hope you can work from that!
The tutorial is very clear
3. Add this code in your page :
<!DOCTYPE html>
<script src="canvasengine-X.Y.Z.all.min.js"></script>
<script src="rpgjs-X.Y.Z.min.js"></script>
<canvas id="canvas_id" width="640" height="480"></canvas>
That's "the canvas" ^
Brian Hellekin post a great tutorial. If you want see all the power of this library you must modify your html file in this way:
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script src="http://canvasengine.net/cdn/dev/canvasengine-latest.all.min.js"></script>
<script src="http://rpgjs.com/cdn/rpgjs-2.0.0.min.js"></script>
<script src="plugins/gamepad.js"></script>
<script src="plugins/virtualjoystick.js"></script>
<script src="plugins/soundmanager2-jsmin.js"></script>
<script>
var Data = null;
if (Data) {
Data.database._scenes = Data.menus;
Data.database.system = Data.settings;
}
RPGJS.defines({
canvas: "canvas",
plugins: ["/rpgjs/plugins/Hub", "/rpgjs/plugins/Arpg", "/rpgjs/plugins/MenuGenerated"],
scene_path: "/rpgjs/",
soundmanager: {
url: "/rpgjs/swf/",
},
autoload: Data == null,
ignoreLoadError: true
}).ready(function(rpg) {
var material_json = {};
var m, type;
if (Data) {
for (var i in Data.materials) {
m = Data.materials[i];
type = m['type'] + "s";
if (!material_json[type]) {
material_json[type] = {};
}
material_json[type][m['material_id']] = RPGJS_Canvas.Materials.getFilename(m['path'], true);
}
var m, e;
var map_info = Data.maps, info;
for (var id in Data.data_maps) {
m = Data.data_maps[id];
this.setMap(id, m.map.map);
info = map_info[id];
info.tileset_id = info.tileset;
info.autotiles_id = info.autotiles;
info.events = [];
info.dynamic_event = [];
for (var i=0 ; i < m.events.length ; i++) {
e = m.events[i];
if (e.data_event.type == "event" ) {
var format =
[
{
"id" : e.event_id,
"x" : e.position_x,
"y" : e.position_y,
"name" : 'EV-' + e.event_id
},
e.data_event.data.pages
];
this.setEvent(id, 'EV-' + e.event_id, format);
info.events.push('EV-' + e.event_id);
}
else {
var name, _id;
for(var key in e.data_event.data) {
_id = e.data_event.data[key];
name = key;
break;
}
info.dynamic_event.push({
"x" : e.position_x,
"y" : e.position_y,
"name" : name,
"id" : _id
});
}
}
}
Data.database.map_infos = map_info;
global.materials = material_json;
global.data = Data.database;
global.game_player.init();
}
var scene = this.scene.call("Scene_Title", {
overlay: true
});
scene.zIndex(0);
});
</script>
<style>
body {
padding: 0;
margin: 0;
overflow: hidden;
background: black;
}
canvas {
-webkit-user-select : none;
-moz-user-select : none;
overflow : hidden;
}
#font-face{
font-family : "RPG";
src : url("Graphics/Fonts/Philosopher-Regular.ttf");
}
#font-face{
font-family : "Megadeth";
src : url("Graphics/Fonts/Elementary_Gothic_Bookhand.ttf");
}
</style>
<div id="game">
<canvas id="canvas" width="640" height="480"></canvas>
</div>
Note:
/rpgjs/ is the folder where i put my project in my local server. For play the game i go to http://localhost/rpgjs/
For work you need to modify the JS file in /plugins/ directory.
/Hub/Sprite_Hub.js
use this path:
array.push(RPGJS.Path.getFile("pictures", "../Pictures/hub.png", "hub"));
array.push(RPGJS.Path.getFile("pictures", "../Pictures/hp_meter.png", "hp_meter"));
array.push(RPGJS.Path.getFile("pictures", "../Pictures/hp_number.png", "hp_number"));
array.push(RPGJS.Path.getFile("pictures", "../Pictures/Hero_Face.png", "hero_face"));
array.push(RPGJS.Path.getFile("pictures", "../Pictures/button_A.png", "button_a"));
array.push(RPGJS.Path.getFile("pictures", "../Pictures/button_B.png", "button_b"));
is very useful debug in the first time with developer tool of google chrome. When you see 404 error on a img resourse just go to your project and search the file name. Find what js script load it and fix path.
And not...the tutorial inside library project is not clear and isn't complete...they just want sell his rpgeditor and js library is put only for promotion purpose i think.
you can see here: http://rpgworld.altervista.org/rpgjs/ my final working project. Is just a try... but you can see the power of this library.
this demo load:
1- a welcome page with new and load came
2- a custom map with one event
3- a working event: a chest that if you open you will find 30 gold
4- a player manu: just click ESC button on keyboard
i don't know how use arpg plugin for a realtime combat event but i working on for understand of to use it.
I'm trying to make a sign that says how many users are currently on my website at a time using Javascript for my Meteor app (I don't know if that makes a difference). I have this code for tracking how many users are on the site at a time:
Presences.find({online: true}).count();
But how do I display this on my website with HTML? I think with the way Meteor is organized it will make things more difficult.
Oh and I should also mention that I have the iron-router package installed in this project so the templates are a little different. This may affect the code?
Its a bit tricky, you need a custom publish function and a virtual collection on the client side (userCount)
This only publishes the count of the number of users online, and changes it as it changes using the Observer query.
This way you don't have to publish all the documents of the users who are online :) so as you have more users it wont slow down your client as much.
Server side code
Meteor.publish("userCount", function() {
var self = this;
var presences = Presences.find({online: true});
var count = presences.count();
var handle = presences.observe({
added: function() {
if(!handle) return;
count++;
self.changed('userCount', 'count', {count: count});
},
removed: function() {
count--;
self.changed('userCount', 'count', {count: count});
}
});
self.added('userCount', 'count', {count: count});
self.onStop(function() {
handle.stop();
});
this.ready();
});
Client side code
var userCount = new Meteor.Collection("userCount");
Meteor.subscribe("userCount");
HTML (example)
<template name="example">
{{userCount}}
</template>
Template Helper
Template.example.userCount = function() {
var count_doc = userCount.findOne({_id:'count'});
return (count_doc && count_doc.count) || 0;
}
Simple, but NOT efficient solution:
PresenceTemplate.js:
Template.PresenceTemplate.helpers({
userCounts:function(){
return Presences.find({online: true}).count();
}
})
PresenceTemplate.html:
<template name="PresenceTemplate">
{{userCounts}}
</template>
I am using Titanium Alloy version 3.2. I have a collection of posts in a listview. My data looks like this:
[
{ username: 'dude', imageUrl: 'url', tags: ['tag1','tag2','tag3'] },
{ username: 'wheres', imageUrl: 'url', tags: ['tag1'] },
{ username: 'my', imageUrl: 'url', tags: ['tag1','tag2','tag3','tag4'] },
{ username: 'car', imageUrl: 'url', tags: ['tag1','tag2'] }
]
And here is the xml. This works only for username and image. I can't figure out how to add the tags to each post.
<ListView id="streamListview">
<Templates>
<ItemTemplate name="template" id="template">
<View class="item-container">
<ImageView bindId="pic" class="pic"/>
<Label bindId="username" class="username"/>
</View>
</ItemTemplate>
</Templates>
<ListSection id="section">
<ListItem template="template" class="list-item"/>
</ListSection>
</ListView>
And my controller code (without the tags)
var posts = [];
for (var i=0; i<data.length; i++){
var post = {
template : "template",
pic : { image : data[i].get("imageUrl") },
username : { text : data[i].get("username") }
};
posts.push(post);
}
$.section.setItems(posts);
How can I add tags (that are clickable) to the post if I am supposed to declare EVERY view in the template before hand? Each tags array in my example would need a different number of views depending on the array length. Each tag would ideally be its own UI.Label element. I believe this can be done using a TableView, but I would prefer using ListView for performance reasons.
I think I know what you need, in this case since you want to generate each item dynamically (for example, a scenario where you open your window with your ListView empty first and make an API call to get remote data and fill the ListView with said data) you will need to use ItemTemplates declared in their own controllers.
You just create a new controller like normal and in the view xml you put your ItemTemplate:
<Alloy>
<ItemTemplate name="template" id="template">
<View class="item-container">
<ImageView bindId="pic" class="pic"/>
<Label bindId="username" class="username"/>
</View>
</ItemTemplate>
</Alloy>
In your tss you put all of the styles referred to each element in your template, since you didn't provide a tss example I can't tell what are your style properties, but in the tss you need to define the style of the template, for example lets say something like:
"#template": // this is the id of your template in your xml
{
width : Ti.UI.FILL,
height : '44dp',
backgroundColor : '#FFFFFF'
}
To fill your ListView with ListItems dynamically, you will need to do something like this in your callback from your API:
function displayListItems(items)
{
var itemCollection = [];
for(var i=0; i < items.length; i++)
{
var tmp = {
pic : {
image : items[i].image
},
username : {
text : items[i].text
},
template : 'template' // here goes the name of the template in your xml, **do not confuse name with id, both are different and using one doesn't replace the other**
};
itemCollection.push(tmp);
}
$.ListView.sections[0].items = itemCollection;
}
And voila, you get your ListView filled dynamically. Now there are some extra steps you can do.
In your template controller you can leave it blank since the ListView can manage the itemclick event, but if you want different actions to take place when a certain element in the Listitem to trigger, you need to specify the functions to be called in your controller for each element.
For example lets say you passed a property called dataInfo to your ImageView and your Label in your template like this:
function displayListItems(items)
{
var itemCollection = [];
for(var i=0; i < items.length; i++)
{
var tmp = {
pic : {
image : items[i].image
dataInfo : items[i].fooA //lets pass to the ImageView the object fooA
},
username : {
text : items[i].text,
dataInfo : items[i].fooB //lets pass to the Label the object fooB
},
template : 'template' // here goes the name of the template in your xml, **do not confuse name with id, both are different and using one doesn't replace the other**
};
itemCollection.push(tmp);
}
$.ListView.sections[0].items = itemCollection;
}
And you want the ImageView and the Label to call different functions, you will need to change your xml like this:
<Alloy>
<ItemTemplate name="template" id="template">
<View class="item-container">
<ImageView bindId="pic" class="pic" onClick="imageFunction"/> <!-- added onClick event -->
<Label bindId="username" class="username" onClick="labelFunction"/> <!-- added onClick event -->
</View>
</ItemTemplate>
</Alloy>
In your controller you will declare each function:
function imageFunction(e)
{
var dataInfo;
if(Ti.Platform.osname === 'android')
{
var item = e.section.items[e.itemIndex];
var bindObject = item[e.bindId];
dataInfo = bindObject.fooA;
}
else
{
dataInfo = e.source.fooA;
}
}
function labelFunction(e)
{
var dataInfo;
if(Ti.Platform.osname === 'android')
{
var item = e.section.items[e.itemIndex];
var bindObject = item[e.bindId];
dataInfo = bindObject.fooB;
}
else
{
dataInfo = e.source.fooB;
}
}
Now you might ask, why do check for the operative system name, well that is because Android and iOS receive different e objects even if you use the same function. In iOS whatever property you pass to the source of the event can be accessed directly with e.source.propertyName while in Android you need to access to the item in e.section using e.itemIndex, after that you retrieve the view inside the item with the e.bindId associated to it.
One of the biggest restrictions on ListItems is updating the views inside a ListItem, to do this you need to update the whole item you want to change visually and assign it a different template, but the speed at which this is done you won't be able to notice any lag, seriously ListView's performance is something else, unlike ScrollView and let's not talk about the horrible and buggy TableView.
A warning, as of Titanium SDK 3.2.0.GA there's a bug in ItemTemplates that causes for views inside child views in the template to change their zIndex in Android with no way to control it, there are two known instances for this:
If you use a don't set the layout in a child view: this could cause for a view that should be displayed beneath another view to come on top of it.
If you use a vertical layout in a child view: this could cause for the positions of each view to be scrambled, this is because zIndex alters the order of display in a vertical layout.
This bug is triggered randomly and the Appcelerator team hasn't put much work on it, check the JIRA ticket here TIMOB-16704.
This can be avoided if you use a template with fixed positioned views and making sure no view comes on top of another, also remember no vertical layouts, haven't tested this with horizontal but personally I try to avoid horizontal layouts since there are other bugs related to it when used in scrollviews, normal views, etc.
EDIT
Another thing you might want to do with this is to assign a different look to the items you render, you have to options:
To apply the styles when you declare the ListItem.
To apply a different layout to each ListItem depending on a series of conditions.
For the first option you need to omit or overwrite the declaration of certain properties in your template:
For example, let's use a different background color where the property fooA exists and another color if it doesn't:
function displayListItems(items)
{
var itemCollection = [];
for(var i=0; i < items.length; i++)
{
var properties = {};
if(typeof items[i].fooA !== 'undefined')
{
properties = {
backgroundColor : 'red'
};
}
else
{
properties = {
backgroundColor : 'blue'
};
}
var tmp = {
properties : properties, // properties for the template
pic : {
image : items[i].image
dataInfo : items[i].fooA //lets pass to the ImageView the object fooA
},
username : {
text : items[i].text,
dataInfo : items[i].fooB //lets pass to the Label the object fooB
},
template : 'template' // here goes the name of the template in your xml, **do not confuse name with id, both are different and using one doesn't replace the other**
};
itemCollection.push(tmp);
}
$.ListView.sections[0].items = itemCollection;
}
You can change width, height, backgroundColor, layout, etc. according to your needs.
Now if you want each item to have a distinct look (meaning different views to display different content) and perhaps a different behavior, you'll need to use different templates.
This might sound bothersome but it is not, templates are fast to create once you get used to them which doesn't take long, another downer might be that if you want 11 different looks, that might mean you'll need 11 templates but that's a extreme case and you might want to rethink your UI if you're dealing with that many templates.
Although restrictive, item templates offer a wide array of options for you to use, a little of imagination is the only ingredient necessary to bring out all of the possibilities.
EDIT 2
I finally understood what was you problem, if you need to create a template whose content changes according to a x variable, then you should try declaring the template on your ListView controller, but this should be done before opening the window were you will be showing the ListView since the templates property can only be set on creation, you should add something like:
function createTemplate(items)
{
var template = {};
for(var i=0; i < items.length; i++)
{
template.childTemplates = [];
for(var j=0; items[i].tags.length; j++)
{
var childTemplate = {
type: 'Ti.UI.Label',
bindId: 'tag' + j,
properties : {
width : Ti.UI.SIZE, // Here you define how your style
height : Ti.UI.SIZE,
font : {
fontSize : '18dp'
},
text : items[i].tags[j].text // you can pass the text here or if you want to on the later for
}
};
template.childTemplates.push(childTemplate);
}
}
// After this you should end up with a template with as many childTemplates as tags each item have, so send the template to the controller with your ListView
Alloy.createController('ListViewWindow', {customTemplate: template});
}
And in your ListView controller you retrieve the template:
var args = arguments[0] || {};
var template = args.customTemplate;
$.ListView.templates = {'customTemplate' : template}; // before any window open call, do this
This should add the template to your ListView, you can also create the ListView in your controller instead of declaring it in your Alloy xml, use the one that fits your needs more.
This should be possible with a ListView if you create the template in the controller dynamically. You would also need to iterate through each "tags" object and generate a Ti.UI.Label "type" for each tag item. However, I'm not certain this method will be more efficient than using a TableView object because essentially every ListItem you create will contain a different template.
To generate a dynamic template it would be similar to this below: Keep in mind you will need to iterate over "tags" and generate x Ti.UI.Label types where x is the length of "tags". Also, the click event should work using Titanium SDK 3.2.1.
var plainTemplate = {
childTemplates: [
{
type: 'Ti.UI.Label',
bindId: 'username'
},
{
type: 'Ti.UI.ImageView',
bindId: 'pic'
},
{
type: 'Ti.UI.Label',
bindId: 'tags',
events: { click : handleTagClickEvent } // Binds a callback to click event
}
]};
function handleTagClickEvent(e) {
Ti.API.info('You clicked a tag label: ' + e.type);
}
var listView = Ti.UI.createListView({
templates: { 'plain': plainTemplate },
defaultItemTemplate: 'plain'
});
Hope this helps you in some way!