Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

Samankaltaiset tiedostot
Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

Ohjelmoin*kielet ja - paradigmat 5op. Markus Norrena

Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

812336A C++ -kielen perusteet,

1.3Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Tuotteiden tiedot: Lisää uuden tuotteen tiedot. Muuta tai poista tuotteen tiedot. Selaa kaikkien tuotteiden tietoja.

FinFamily Installation and importing data ( ) FinFamily Asennus / Installation

1.3 Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Choose Finland-Helsinki Valitse Finland-Helsinki

Kirjasto Relaatiotietokannat Kevät Auvinen Annemari Niemi Anu Passoja Jonna Pulli Jari Tersa Tiina

Capacity Utilization

Alternative DEA Models

The CCR Model and Production Correspondence

Salasanan vaihto uuteen / How to change password

Tietorakenteet ja algoritmit

On instrument costs in decentralized macroeconomic decision making (Helsingin Kauppakorkeakoulun julkaisuja ; D-31)

On instrument costs in decentralized macroeconomic decision making (Helsingin Kauppakorkeakoulun julkaisuja ; D-31)

Järjestelmän syötteet ja tulosteet Kohahdus Helsinki Ohjelmistotuotantoprojekti HELSINGIN YLIOPISTO Tietojenkäsittelytieteen laitos

Tehtävä 1. Tietojen lisääminen, poistaminen, päivittäminen ja tulostaminen

Toisessa viikkoharjoituksessa on tavoitteena tutustua JUnit:lla testaukseen Eclipse-ympäristössä.

KONEISTUSKOKOONPANON TEKEMINEN NX10-YMPÄRISTÖSSÄ

Other approaches to restrict multipliers

Sisällysluettelo Table of contents

RINNAKKAINEN OHJELMOINTI A,

C++11 seminaari, kevät Johannes Koskinen

Lab SBS3.FARM_Hyper-V - Navigating a SharePoint site

Olio-ohjelmointi Javalla

Gap-filling methods for CH 4 data

2 3 LIITE 2. Index.php 1 (10) 4 5 <?php 6 7 /*! \mainpage Artikkelihallintaohjelma 8 * 9 * \section intro_sec Introduction 10 * 11 * Tämän on

Tapahtumakalenteri & Jäsentietojärjestelmä Ylläpito

Tietokannat. CREATE TABLE table(col1,col2,... ); Luo uuden taulun. CREATE TABLE opiskelijat(opnumero,etunimi,sukunimi);

XNA grafiikka laajennus opas. Paavo Räisänen. Tämän oppaan lähdekoodit ovat ladattavissa näiden sivujen Ladattavat osiossa.

Hankkeiden vaikuttavuus: Työkaluja hankesuunnittelun tueksi

Web Services tietokantaohjelmoinnin perusteet

1. SIT. The handler and dog stop with the dog sitting at heel. When the dog is sitting, the handler cues the dog to heel forward.

Ohjelmointi 2 / 2010 Välikoe / 26.3

Mobility Tool. Demo CIMO

Johdanto PHP PostgreSQL. PHP & PostgreSQL. Paul Tötterman 5. helmikuuta PHP & PostgreSQL.

HSMT Tietokannoista. Ville Leppänen. HSMT, c Ville Leppänen, IT, Turun yliopisto, 2008 p.1/32

Proseduurit, funktiot ja herättimet - esimerkkeinä Oracle, SQL Server, MySQL ja OCELOT. Jouni Huotari S2008

PROSEDUURIT, FUNKTIOT JA HERÄTTIMET - ESIMERKKEINÄ ORACLE, SQL SERVER, MYSQL JA OCELOT JOUNI HUOTARI K2009

Returns to Scale II. S ysteemianalyysin. Laboratorio. Esitelmä 8 Timo Salminen. Teknillinen korkeakoulu

SIMULINK S-funktiot. SIMULINK S-funktiot

Käyttäjienhallintatyökalu

Tietokannat II -kurssin harjoitustyö

Kylänetti projektin sivustojen käyttöohjeita Dokumentin versio 2.10 Historia : 1.0, 1.2, 1.6 Tero Liljamo / Deserthouse, päivitetty 25.8.

Efficiency change over time

Use of spatial data in the new production environment and in a data warehouse

Palvelinpuolen ohjelmointi

SQL - STRUCTURED QUERY LANGUAGE

TIEKE Verkottaja Service Tools for electronic data interchange utilizers. Heikki Laaksamo

REST rajapintana mobiilikehityksessä

Java ja tietokannan käsittely (JDBC)

FinFamily PostgreSQL installation ( ) FinFamily PostgreSQL

P e d a c o d e ohjelmointikoulutus verkossa

1. Liikkuvat määreet

anna minun kertoa let me tell you

Tiedonhallinnan perusteet. H11 Ovien ja kulun valvontajärjestelmän tietokanta

Ohjelmoinnin peruskurssien laaja oppimäärä

TIETOKANNAT: MYSQL & POSTGRESQL Seminaarityö

Tietokannat. CREATE TABLE table(col1,col2,... ); Luo uuden taulun. CREATE TABLE opiskelijat(opnumero,etunimi,sukunimi);

HELIA 1 (15) Outi Virkki Tietokantasuunnittelu

Harjoituksen aiheena on tietokantapalvelimen asentaminen ja testaaminen. Asennetaan MySQL-tietokanta. Hieman linkkejä:

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

C470E9AC686C

List-luokan soveltamista. Listaan lisääminen Listan läpikäynti Listasta etsiminen Listan sisällön muuttaminen Listasta poistaminen Listan kopioiminen

Asiakaspalautteen merkitys laboratoriovirheiden paljastamisessa. Taustaa

HELIA 1 (11) Outi Virkki Tiedonhallinta

Bounds on non-surjective cellular automata

Javascript 2: Ohjelmointikielen ominaisuudet. Jaana Holvikivi Metropolia

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

Ylläpitodokumentti. PLAYOFF Jari Anttila Sanna Fröblom Aarno Sandvik Tommi Paavilainen Miikka Kohijoki. Päivi Pääkkö, ohjaaja

Tietokannat. CREATE TABLE table(col1,col2,... ); Luo uuden taulun. CREATE TABLE opiskelijat(opnumero,etunimi,sukunimi);

OSA III PHP:n käyttö. Oppitunti

HELIA 1 (14) Outi Virkki Tiedonhallinta

Periytyminen (inheritance)

Ohjelmoinnin peruskurssien laaja oppimäärä

Rekisteröiminen - FAQ

Rinnakkaisohjelmointi kurssi. Opintopiiri työskentelyn raportti

CSE-A1200 Tietokannat

OFFICE 365 OPISKELIJOILLE

Ohjelmistojen mallintamisen ja tietokantojen perusteiden yhteys

Täysautomatisoitu raportointiympäristö. Joni-Petteri Paavilainen Jani Alatalo

Luokan sisällä on lista

LANSEERAUS LÄHESTYY AIKATAULU OMINAISUUDET. Sähköinen jäsenkortti. Yksinkertainen tapa lähettää viestejä jäsenille

Ohjelmointi 1 C#, kevät 2013, 2. tentti

16. Allocation Models

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

9/11/2015 MOBILITY TOOL+ ERASMUS+ Learning Mobility of Individuals. M a n a g e m e n t. I s s u e. T o o l

Tällä viikolla. Kotitehtävien läpikäynti Aloitetaan Pelifirman tietovaraston suunnittelu Jatketaan SQL-harjoituksia

koodipolku iteraation muokkauksessa Dokumentti: koodipolkuesimerkki.doc Päiväys: Projekti : AgileElephant

Metodien tekeminen Javalla

JULKAISUJÄRJESTELMÄ MARKKINOINTISIVUN TOTEUTUKSESSA

Mikä yhteyssuhde on?

Insert lauseella on kaksi muotoa: insert into taulu [(sarakenimet)] values (arvot)

Listarakenne (ArrayList-luokka)

ICT1TN004. Lomakkeet. Heikki Hietala

Transkriptio:

Ohjelmointikielet ja -paradigmat 5op Markus Norrena

Kotitehtävä 10: Framework esitelmä Valmistelkaa viimeiselle kerralle esitelmä Frameworkistanne Tarkoituksena antaa muille kurssilaisille käsitys Frameworkinne toiminnallisuuksista sekä sen hyvistä ja huonoista puolista Esitelkää myös mitä olette saaneet sillä aikaan Esitelmät 10.4. (Nic) ja 17.4. (muut)

Näitä jatketaan tänään Käyttäjien luominen, päivittäminen ja poistaminen (creating, updating and deleting users): User luokkaan Tiedostojen latauksen (File Upload): Photograph -luokka Kuvien näyttäminen ja niiden ylläpito (jos ehditään)

Ko$tehtävä Toteuta public func$on update() ja public func$on delete() User luokkaan seuraavien kalvojen mukaan:

user.php: public func$on update() Luodaan samalla tapaa, muba sql- muoto on: UPDATE table SET key='value', key='value' WHERE condition Toteuta. Voisi toimia esim. näin: $user = User::find_by_id(2); $user->password = "uusisalasana"; $user->update();

user.php: public func$on update() public function update() { global $database; // - UPDATE table SET key='value', key='value' WHERE condition $sql = "UPDATE ".self::$table_name." SET "; $sql.= "username='". $database->escape_value($this->username)."', "; $sql.= "password='". $database->escape_value($this->password)."', "; $sql.= "first_name='". $database->escape_value($this->first_name)."', "; $sql.= "last_name='". $database->escape_value($this->last_name)."' "; $sql.= "WHERE id=". $database->escape_value($this->id); $database->query($sql); return ($database->affected_rows() == 1)? true : false;

user.php: public func$on delete() public func$on delete() SQL- lauseen muoto DELETE FROM table WHERE condition LIMIT 1 Limit - kannabaa $etoturvasyistä käybää delete- lauseissa aina kun mahdollista. Huomatkaa ebä ilmentymä (olio) on yhä olemassa vaikka poistamme käybäjän $etokannasta! Tämä mahdollistaisi esim. Tällaisen: echo $user->first_name. " was deleted";

user.php: public func$on delete() public function delete() { global $database; // - DELETE FROM table WHERE condition LIMIT 1 // - use LIMIT 1 $sql = "DELETE FROM ".self::$table_name; $sql.= " WHERE id=". $database->escape_value($this->id); $sql.= " LIMIT 1"; $database->query($sql); return ($database->affected_rows() == 1)? true : false; // NB: After deleting, the instance of User still // exists, even though the database entry does not.

Seuraavaksi samaa valokuville! Ensin kuvien lataaminen (upload) SiBen create, update and delete photographs (CRUD): photograph.php photograph.php

Luo valokuville $etokanta CREATE TABLE photographs ( id int(11) NOT NULL auto_increment, filename varchar(255) NOT NULL, type varchar(100) NOT NULL, size int(11) NOT NULL, caption varchar(255) NOT NULL, PRIMARY KEY (id) );

Luo palvelimelle kansio ladabaville /public/images kuville Muutetaan webbipalvelin kansion omistajaksi sudo chown www- data images Jos www- data on apache- prosessin käybäjätunnus Ja tarkista ebä oikeudet (chmod) ovat muuten kunnossa Muistakaa e*ä tämä teh.in jo ekalla luennolla.

Perus upload- käskyt: lomake <form action="upload.php" enctype="multipart/form-data" method="post"> <input type="hidden" name="max_file_size" value="1000000" /> <input type="file" name="file_upload" /> <input type="submit" name="submit" value="upload" /> </form> Muistakaa e*ä tämä teh.in jo ekalla luennolla.

Perus upload- käskyt PHP: $_FILES[] if(isset($_post['submit'])) { // process the form data $tmp_file = $_FILES['file_upload']['tmp_name']; $target_file = basename($_files['file_upload']['name']); $upload_dir = "images"; // move_uploaded_file will return false if $tmp_file is // not a valid upload file or if it cannot be moved for // any other reason if(move_uploaded_file($tmp_file, $upload_dir."/".$target_file)){ echo "File uploaded successfully."; else { echo $_FILES['file_upload']['error']; Muistakaa e*ä tämä teh.in jo ekalla luennolla (tallenna.php)

photograph.php photograph ja user - luokkien määribelyissä paljon samankaltaisuuksia. Lisää / poista osien toimintaperiaabeet voi kopioida user- luokasta. Lisää photograph.php ini$alizeen (ini$alize.php) joba se ladataan muiden luokkien kanssa

class Photograph { photograph.php Käytän itse luokassa tällaisia abribuubeja protected static $table_name="photographs"; protected static $db_fields=array('id', 'filename', 'type', 'size', 'caption'); public $id; public $filename; public $type; public $size; public $caption; private $temp_path; protected $upload_dir="images"; public $errors=array();

Lomake jolla testata luokka: photo_upload.php osa 1 require_once('../../includes/initialize.php'); if (!$session->is_logged_in()) { redirect_to("login.php"); $max_file_size = 1048576; // expressed in bytes if(isset($_post['submit'])) { $photo = new Photograph(); $photo->caption = $_POST['caption']; $photo->attach_file($_files['file_upload']); if($photo->save()) { // Success $session->message("photograph uploaded successfully."); // redirect_to('list_photos.php'); // implement later else { // Failure $message = join("<br />", $photo->errors);

<?php include_layout_template('admin_header.php');?> <h2>photo Upload</h2> Lomake jolla testata luokka: photo_upload.php osa 2 <?php echo output_message($message);?> <form action="photo_upload.php" enctype="multipart/form-data" method="post"> <input type="hidden" name="max_file_size" value="<?php echo $max_file_size;?>" /> <p><input type="file" name="file_upload" /></p> <p>caption: <input type="text" name="caption" value="" /></p> <input type="submit" name="submit" value="upload" /> </form> <?php include_layout_template('admin_footer.php');?>

photograph.php // Pass in $_FILE(['uploaded_file']) as an argument public function attach_file($file) { // Define $temp_path, $filename, $type, $size // Also check for errors Metodi joka alustaa olion kun $edot ovat oliossa, ne voi tallebaa $etokantaan (myöhemmin, sitä ei kannata vielä mie_ä) Toteuta

photograph.php // Pass in $_FILE(['uploaded_file']) as an argument public function attach_file($file) { // Perform error checking on the form parameters if(!$file empty($file)!is_array($file)) { // error: nothing uploaded or wrong argument usage $this->errors[] = "No file was uploaded."; return false; elseif($file['error']!= 0) { // error: report what PHP says went wrong $this->errors[] = $file['error']; return false; else { // Set object attributes to the form parameters. $this->temp_path = $file['tmp_name']; $this->filename = basename($file['name']); $this->type = $file['type']; $this->size = $file['size']; return true;

photograph.php public function save() { Save metodi tekee kaksi asiaa Siirtää kuvan sen oikeaan talletuspaikkaan TalleBaa $etokantaan $edon minne kuva tallete_in

photograph.php osa 1 public function save() { // A new record won't have an id yet. if(isset($this->id)) { $this->update(); else { // Can't save if there are pre-existing errors if(!empty($this->errors)) { return false; // Make sure the caption is not too long for the DB if(strlen($this->caption) > 255) { $this->errors[] = "The caption can only be 255 characters long."; return false; // Can't save without filename and temp location if(empty($this->filename) empty($this->temp_path)) { $this->errors[] = "The file location was not available."; return false; // Determine the target_path $target_path = SITE_ROOT.DS. 'public'.ds. $this->upload_dir. DS. $this->filename;

photograph.php osa 2 // Make sure a file doesn't already exist in the target location if(file_exists($target_path)) { $this->errors[] = "The file {$this->filename already exists."; return false; // Attempt to move the file if(move_uploaded_file($this->temp_path, $target_path)) { // Success // Save a corresponding entry to the database if($this->create()) { // We are done with temp_path, the file isn't there anymore unset($this->temp_path); return true; else { // File was not moved. $this->errors[] = "The file upload failed."; return false;

photograph.php public function create() { global $database; // - INSERT INTO table (key, key) VALUES ('value', 'value') // OSAATKO ITSE TEHDÄ TÄMÄN?

photograph.php public function create() { global $database; // - INSERT INTO table (key, key) VALUES ('value', 'value') // - single-quotes around all values // - escape all values to prevent SQL injection $attributes = $this->sanitized_attributes(); $sql = "INSERT INTO ".self::$table_name." ("; $sql.= join(", ", array_keys($attributes)); $sql.= ") VALUES ('"; $sql.= join("', '", array_values($attributes)); $sql.= "')"; if($database->query($sql)) { $this->id = $database->insert_id(); return true; else { return false;

photograph.php protected function sanitized_attributes() { global $database; $clean_attributes = array(); // sanitize the values before submitting // Note: does not alter the actual value of each attribute foreach($this->attributes() as $key => $value){ $clean_attributes[$key] = $database->escape_value($value); return $clean_attributes;

photograph.php protected function attributes() { // return an array of attribute names and their values $attributes = array(); foreach(self::$db_fields as $field) { if(property_exists($this, $field)) { $attributes[$field] = $this->$field; return $attributes;

Kokeile toimiiko? photograph.php

list_photos.php NäyBää kuvat admin puolella - > yritä ensin kirjoibaa se itse! Saatat tarvita user luokasta tubuja apumetodeja, kuten Photograph::find_all();

list_photos.php osa 1 <?php require_once("../../includes/initialize.php"); if (!$session->is_logged_in()) { redirect_to("login.php"); // Find all the photos $photos = Photograph::find_all(); include_layout_template('admin_header.php');?> <h2>photographs</h2> <?php echo output_message($message);?> <table class="bordered"> <tr> <th>image</th> <th>filename</th> <th>caption</th> <th>size</th> <th>type</th> <th> </th> </tr>

list_photos.php osa 2 <?php foreach($photos as $photo):?> <tr> <td><img src="../<?php echo $photo->image_path();?>" width="100" /></td> <td><?php echo $photo->filename;?></td> <td><?php echo $photo->caption;?></td> <td><?php echo $photo->size_as_text();?></td> <td><?php echo $photo->type;?></td> <td><a href="delete_photo.php?id=<?php echo $photo->id;?> ">Delete</a></td> </tr> <?php endforeach;?> </table> <br /> <a href="photo_upload.php">upload a new photograph</a> <?php include_layout_template('admin_footer.php');?>

photograph.php public function image_path() { return $this->upload_dir.ds.$this->filename; public function size_as_text() { if($this->size < 1024) { return "{$this->size bytes"; elseif($this->size < 1048576) { $size_kb = round($this->size/1024); return "{$size_kb KB"; else { $size_mb = round($this->size/1048576, 1); return "{$size_mb MB";

public static function find_by_sql($sql="") { global $database; $result_set = $database->query($sql); $object_array = array(); while ($row = $database->fetch_array($result_set)) { $object_array[] = self::instantiate($row); return $object_array; photograph.php // Common Database Methods public static function find_all() { return self::find_by_sql("select * FROM ".self::$table_name); public static function find_by_id($id=0) { global $database; $result_array = self::find_by_sql( "SELECT * FROM ".self::$table_name." WHERE id=".$database->escape_value($id)." LIMIT 1"); return!empty($result_array)? array_shift($result_array) : false;

photograph.php private static function instantiate($record) { $object = new self; // Simple, long-form approach: $object->id = $record['id']; $object->filename = $record['filename']; $object->type = $record['type']; $object->size = $record['size']; $object->caption = $record['caption']; return $object;

delete_photo.php Koodissa mainibu delete_photo.php pitäisi myös toteubaa

delete_photo.php <?php require_once("../../includes/initialize.php"); php if (!$session->is_logged_in()) { redirect_to("login.php"); // must have an ID if(empty($_get['id'])) { $session->message("no photograph ID was provided."); redirect_to('index.php'); $photo = Photograph::find_by_id($_GET['id']); if($photo && $photo->destroy()) { $session->message("the photo {$photo->filename was deleted."); redirect_to('list_photos.php'); else { $session->message("the photo could not be deleted."); redirect_to('list_photos.php'); if(isset($database)) { $database->close_connection();?>

photograph.php public function destroy() { // First remove the database entry if($this->delete()) { // then remove the file // Even though the database entry is gone, this object // is still around $target_path = SITE_ROOT.DS.'public'.DS.$this->image_path(); return unlink($target_path)? true : false; else { // database delete failed return false;

photograph.php public function delete() { global $database; // - DELETE FROM table WHERE condition LIMIT 1 $sql = "DELETE FROM ".self::$table_name; $sql.= " WHERE id=". $database->escape_value($this->id); $sql.= " LIMIT 1"; $database->query($sql); return ($database->affected_rows() == 1)? true : false;

public $message; function construct() { $this->check_message(); public function message($msg="") { if(!empty($msg)) {// set message $_SESSION['message'] = $msg; else { // get message return $this->message; session.php lisätään $message, joba saadaan viestejä välitebyä redirect käskyn yli private function check_message() { if(isset($_session['message'])) { $this->message = $_SESSION['message']; unset($_session['message']); else { $this->message = ""; $message = $session->message();

photograph.php public function update() { global $database; // - UPDATE table SET key='value', key='value' WHERE condition $attributes = $this->sanitized_attributes(); $attribute_pairs = array(); foreach($attributes as $key => $value) { $attribute_pairs[] = "{$key='{$value'"; $sql = "UPDATE ".self::$table_name." SET "; $sql.= join(", ", $attribute_pairs); $sql.= " WHERE id=". $database->escape_value($this->id); $database->query($sql); return ($database->affected_rows() == 1)? true : false;

Julkiselle puolelle Sivun vierailijoille näytetään kuvat: index.php pienemmille kuvakkeille photo.php täysikokoisille kuville Kokeile toteubaa.

Julkiselle puolelle: index.php <?php require_once("../includes/initialize.php");?> <?php // Find all photos $photos = Photograph::find_all();?> <?php include_layout_template('header.php');?> <?php foreach($photos as $photo):?> <div style="float: left; margin-left: 20px;"> <a href="photo.php?id=<?php echo $photo->id;?>"> <img src="<?php echo $photo->image_path();?>" width="200"> </a> <p><?php echo $photo->caption;?></p> </div> <?php endforeach;?> <?php include_layout_template('footer.php');?>

Julkiselle puolelle: photo.php <?php require_once("../includes/initialize.php"); if(empty($_get['id'])) { $session->message("no photograph ID was provided."); redirect_to('index.php'); $photo = Photograph::find_by_id($_GET['id']); if(!$photo) { $session->message("the photo could not be located."); redirect_to('index.php'); include_layout_template('header.php');?> <a href="index.php">«back</a><br /><br /> <div style="margin-left: 20px;"> <img src="<?php echo $photo->image_path();?>" /> <p><?php echo $photo->caption;?></p> </div> <?php include_layout_template('footer.php');?>

(Kotitehtävä) Toteuta kommentointi (Comments -class) Pitkälti samalla tapaa kuin käyttäjien ja kuvien ylläpito Erona että kommentti liittyy aina tiettyyn kuvaan

(Kotitehtävä) Toteuta admin-puolelle lomake joka mahdollistaa käyttäjien ylläpidon uuden luomisen, delete & update työkalut?

Photo Gallery Project Alkuperäisen suunnitelmani $lanne: User Photograph Comments - > (ko$tehtävä) Database Session Pagina$on - >jää puubumaan

Kurssin status KäsiBelemäBä jäi Func$onal Programming (Funk$onaalinen ohjelmoin$) hbp://phpmaster.com/func$onal- programming- and- php/ Haskell is an advanced purely- func$onal programming language. hbp://www.haskell.org/haskellwiki/haskell

Kurssin status Muuta jos kiinnostaa JavaScript JavaScript does not have classical OOP. It has prototyping OOP. This means you have only objects. h*p://www.codecademy.com/tracks/javascript The MVC PaBern and PHP hbp://phpmaster.com/the- mvc- pabern- and- php- 1/ hbp://phpmaster.com/the- mvc- pabern- and- php- 2/

KurssipalauteBa Tuubissa nyt jo! Ensi kerralla "framework" esitelmät.