<commit_before>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;

const apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
const sessionIds = new Map();

function processEvent(event) {
    var sender = event.sender.id.toString();

    if ((event.message && event.message.text) || (event.postback && event.postback.payload)) {
        var text = event.message ? event.message.text : event.postback.payload;
        // Handle a text message from this sender

        if (!sessionIds.has(sender)) {
            sessionIds.set(sender, uuid.v1());
        }

        console.log("Text", text);

        let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender)
            });

        apiaiRequest.on('response', (response) => {
            if (isDefined(response.result)) {
                //asd
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let action = response.result.action;
                /*console.log(responseText);
                 console.log(responseData);
                 console.log(action);*/
                if (isDefined(responseData) && isDefined(responseData.facebook)) {
                    if (!Array.isArray(responseData.facebook)) {
                        try {
                            console.log('Response as formatted message');
                            sendFBMessage(sender, responseData.facebook);
                        } catch (err) {
                            sendFBMessage(sender, {text: err.message});
                        }
                    } else {
                        responseData.facebook.forEach((facebookMessage) => {
                            try {
                                if (facebookMessage.sender_action) {
                                    console.log('Response as sender action');
                                    sendFBSenderAction(sender, facebookMessage.sender_action);
                                }
                                else {
                                    console.log('Response as formatted message');
                                    sendFBMessage(sender, facebookMessage);
                                }
                            } catch (err) {
                                sendFBMessage(sender, {text: err.message});
                            }
                        });
                    }
                    //} else if (isDefined(responseText)) {
                } else if (isDefined(action) && isDefined(responseText)) {
                    console.log('Response as text message');
                    // facebook API limit for text length is 320,
                    // so we must split message if needed
                    var splittedText = splitResponse(responseText);
                    /*switch (action){
                     case 'show_prod':
                     console.log('generic');
                     async.eachSeries(splittedText, (textPart, callback) => {
                     sendFBMessage(sender, {text: textPart}, callback);
                     });
                     sendFBMessage(sender, generic_message, null);
                     break;
                     default:
                     console.log('default');
                     async.eachSeries(splittedText, (textPart, callback) => {
                     sendFBMessage(sender, {text: textPart}, callback);
                     });
                     }*/
                    async.eachSeries(splittedText, (textPart, callback) => {
                        sendFBMessage(sender, {text: textPart}, callback);
                    });
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }
}

function splitResponse(str) {
    if (str.length <= 320) {
        return [str];
    }

    return chunkString(str, 300);
}

function chunkString(s, len) {
    var curr = len, prev = 0;

    var output = [];

    while (s[curr]) {
        if (s[curr++] == ' ') {
            output.push(s.substring(prev, curr));
            prev = curr;
            curr += len;
        }
        else {
            var currReverse = curr;
            do {
                if (s.substring(currReverse - 1, currReverse) == ' ') {
                    output.push(s.substring(prev, currReverse));
                    prev = currReverse;
                    curr = currReverse + len;
                    break;
                }
                currReverse--;
            } while (currReverse > prev)
        }
    }
    output.push(s.substr(prev));
    //console.log(output);
    //output = ['Length of param message[text] must be less than or equal to 320'];
    return output;
}

/*const generic_message = {
 attachment: {
 type: "template",
 payload: {
 template_type: "generic",
 elements: [{
 title: "rift",
 subtitle: "Next-generation virtual reality",
 item_url: "https://www.oculus.com/en-us/rift/",
 image_url: "http://messengerdemo.parseapp.com/img/rift.png",
 buttons: [{
 type: "web_url",
 url: "https://www.oculus.com/en-us/rift/",
 title: "Open Web URL"
 }, {
 type: "postback",
 title: "Call Postback",
 payload: "Payload for first bubble"
 }]
 }, {
 title: "touch",
 subtitle: "Your Hands, Now in VR",
 item_url: "https://www.oculus.com/en-us/touch/",
 image_url: "http://messengerdemo.parseapp.com/img/touch.png",
 buttons: [{
 type: "web_url",
 url: "https://www.oculus.com/en-us/touch/",
 title: "Open Web URL"
 }, {
 type: "postback",
 title: "Call Postback",
 payload: "Payload for second bubble"
 }]
 }]
 }
 }
 };*/

function sendFBMessage(sender, messageData, callback) {
    //console.log(sender);
    request({
        url: 'https://graph.facebook.com/v2.6/me/messages',
        qs: {access_token: FB_PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: sender},
            message: messageData
            //message: generic_message
        }
    }, (error, response, body) => {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }

        if (callback) {
            callback();
        }
    });
}

function sendFBSenderAction(sender, action, callback) {
    setTimeout(() => {
        request({
            url: 'https://graph.facebook.com/v2.6/me/messages',
            qs: {access_token: FB_PAGE_ACCESS_TOKEN},
            method: 'POST',
            json: {
                recipient: {id: sender},
                sender_action: action
            }
        }, (error, response, body) => {
            if (error) {
                console.log('Error sending action: ', error);
            } else if (response.body.error) {
                console.log('Error: ', response.body.error);
            }
            if (callback) {
                callback();
            }
        });
    }, 1000);
}

function doSubscribeRequest() {
    request({
            method: 'POST',
            uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
        },
        (error, response, body) => {
            if (error) {
                console.error('Error while subscription: ', error);
            } else {
                console.log('Subscription result: ', response.body);
            }
        });
}

function isDefined(obj) {
    if (typeof obj == 'undefined') {
        return false;
    }

    if (!obj) {
        return false;
    }

    return obj != null;
}

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);
        var asd = data;
        //console.log(asd);
        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        //console.log(event);
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.post('/webhook_apiai/', (req, res) => {

    //var weather_query = new Boolean(false);
    var weather_query = false;

    const generic_message = {
        attachment: {
            type: "template",
            payload: {
                template_type: "generic",
                elements: [{
                    title: "Fresh Pizza",
                    subtitle: "Be carefull and don't eat your fingers!!!",
                    item_url: "https://en.wikipedia.org/wiki/Pizza",
                    //image_url: "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg",
                    buttons: [{
                        type: "web_url",
                        url: "https://en.wikipedia.org/wiki/Pizza",
                        title: "Open Web URL"
                    }/*, {
                     type: "postback",
                     title: "Call Postback",
                     payload: "Payload for first bubble"
                     }*/]
                }]
            }
        }
    };

    try {
        var data = JSONbig.parse(req.body);
        //console.log(data);
        switch(data.result.action){
            case 'show_prod':
                console.log('pizza');
                switch(data.result.parameters.pizza_type){
                    case 'Margherita':
                        generic_message.attachment.payload.elements[0].title = data.result.parameters.pizza_type;
                        generic_message.attachment.payload.elements[0].image_url = "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg";
                }
                break;
            case 'show_weather':
                console.log('weather');

                if(isDefined(data.result.parameters['geo-city']) == true || isDefined(data.result.contexts.parameters['geo-city']) == true){
                    if (isDefined(data.result.parameters['geo-city']) == true) {
                        var city = data.result.parameters['geo-city'];
                    } else {
                        var city = data.result.parameters.contexts['geo-city'];
                    }
                    var base_url = "https://query.yahooapis.com/v1/public/yql?" + "q=select+%2A+from+weather.forecast+where+woeid+in+%28select+woeid+from+geo.places%281%29+where+text%3D%27"+city+"%27%29" + "&format=json";
                    weather_query = true;
                    request({
                        url: base_url,
                        method: 'GET',
                        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                        contentType :'application/x-www-form-urlencoded',
                    }, function(error, response, body){
                        if(error) {
                            console.log(error);
                            return res.status(400).json({
                                status: "error",
                                error: error
                            });
                        } else {
                            var query = JSON.parse(body).query;
                            var results = query.results;
                            var channel = results.channel;
                            var item = channel.item;
                            var location = channel.location;
                            var units = channel.units;
                            var condition = item.condition;
                            //console.log(channel)
                            var string = "Today in " + location.city + ": " + condition.text + ", the temperature is " + condition.temp + " " + units.temperature
                            generic_message.attachment.payload.elements[0].title = 'Weather in' + city;
                            generic_message.attachment.payload.elements[0].subtitle = string;
                            generic_message.attachment.payload.elements[0].item_url = channel.link;
                            generic_message.attachment.payload.elements[0].image_url = channel.image.url;
                            generic_message.attachment.payload.elements[0].buttons[0].url = channel.link;
                            return res.status(200).json({
                                data: {
                                    facebook: generic_message
                                }
                            });
                        }
                    });
                } else {
                    return res.status(200).json({
                        data: {
                            facebook: {text: 'no city'}
                        }
                    });
                }
                break;
        }
        console.log(weather_query);
        if (weather_query != true) {
            return res.status(200).json({
                data: {
                    facebook: generic_message
                }
            });
        }
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }
});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

doSubscribeRequest();<commit_msg>'webhook_test'<commit_after>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;

const apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
const sessionIds = new Map();

function processEvent(event) {
    var sender = event.sender.id.toString();

    if ((event.message && event.message.text) || (event.postback && event.postback.payload)) {
        var text = event.message ? event.message.text : event.postback.payload;
        // Handle a text message from this sender

        if (!sessionIds.has(sender)) {
            sessionIds.set(sender, uuid.v1());
        }

        console.log("Text", text);

        let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender)
            });

        apiaiRequest.on('response', (response) => {
            if (isDefined(response.result)) {
                //asd
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let action = response.result.action;
                /*console.log(responseText);
                 console.log(responseData);
                 console.log(action);*/
                if (isDefined(responseData) && isDefined(responseData.facebook)) {
                    if (!Array.isArray(responseData.facebook)) {
                        try {
                            console.log('Response as formatted message');
                            sendFBMessage(sender, responseData.facebook);
                        } catch (err) {
                            sendFBMessage(sender, {text: err.message});
                        }
                    } else {
                        responseData.facebook.forEach((facebookMessage) => {
                            try {
                                if (facebookMessage.sender_action) {
                                    console.log('Response as sender action');
                                    sendFBSenderAction(sender, facebookMessage.sender_action);
                                }
                                else {
                                    console.log('Response as formatted message');
                                    sendFBMessage(sender, facebookMessage);
                                }
                            } catch (err) {
                                sendFBMessage(sender, {text: err.message});
                            }
                        });
                    }
                    //} else if (isDefined(responseText)) {
                } else if (isDefined(action) && isDefined(responseText)) {
                    console.log('Response as text message');
                    // facebook API limit for text length is 320,
                    // so we must split message if needed
                    var splittedText = splitResponse(responseText);
                    /*switch (action){
                     case 'show_prod':
                     console.log('generic');
                     async.eachSeries(splittedText, (textPart, callback) => {
                     sendFBMessage(sender, {text: textPart}, callback);
                     });
                     sendFBMessage(sender, generic_message, null);
                     break;
                     default:
                     console.log('default');
                     async.eachSeries(splittedText, (textPart, callback) => {
                     sendFBMessage(sender, {text: textPart}, callback);
                     });
                     }*/
                    async.eachSeries(splittedText, (textPart, callback) => {
                        sendFBMessage(sender, {text: textPart}, callback);
                    });
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }
}

function splitResponse(str) {
    if (str.length <= 320) {
        return [str];
    }

    return chunkString(str, 300);
}

function chunkString(s, len) {
    var curr = len, prev = 0;

    var output = [];

    while (s[curr]) {
        if (s[curr++] == ' ') {
            output.push(s.substring(prev, curr));
            prev = curr;
            curr += len;
        }
        else {
            var currReverse = curr;
            do {
                if (s.substring(currReverse - 1, currReverse) == ' ') {
                    output.push(s.substring(prev, currReverse));
                    prev = currReverse;
                    curr = currReverse + len;
                    break;
                }
                currReverse--;
            } while (currReverse > prev)
        }
    }
    output.push(s.substr(prev));
    //console.log(output);
    //output = ['Length of param message[text] must be less than or equal to 320'];
    return output;
}

/*const generic_message = {
 attachment: {
 type: "template",
 payload: {
 template_type: "generic",
 elements: [{
 title: "rift",
 subtitle: "Next-generation virtual reality",
 item_url: "https://www.oculus.com/en-us/rift/",
 image_url: "http://messengerdemo.parseapp.com/img/rift.png",
 buttons: [{
 type: "web_url",
 url: "https://www.oculus.com/en-us/rift/",
 title: "Open Web URL"
 }, {
 type: "postback",
 title: "Call Postback",
 payload: "Payload for first bubble"
 }]
 }, {
 title: "touch",
 subtitle: "Your Hands, Now in VR",
 item_url: "https://www.oculus.com/en-us/touch/",
 image_url: "http://messengerdemo.parseapp.com/img/touch.png",
 buttons: [{
 type: "web_url",
 url: "https://www.oculus.com/en-us/touch/",
 title: "Open Web URL"
 }, {
 type: "postback",
 title: "Call Postback",
 payload: "Payload for second bubble"
 }]
 }]
 }
 }
 };*/

function sendFBMessage(sender, messageData, callback) {
    //console.log(sender);
    request({
        url: 'https://graph.facebook.com/v2.6/me/messages',
        qs: {access_token: FB_PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: sender},
            message: messageData
            //message: generic_message
        }
    }, (error, response, body) => {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }

        if (callback) {
            callback();
        }
    });
}

function sendFBSenderAction(sender, action, callback) {
    setTimeout(() => {
        request({
            url: 'https://graph.facebook.com/v2.6/me/messages',
            qs: {access_token: FB_PAGE_ACCESS_TOKEN},
            method: 'POST',
            json: {
                recipient: {id: sender},
                sender_action: action
            }
        }, (error, response, body) => {
            if (error) {
                console.log('Error sending action: ', error);
            } else if (response.body.error) {
                console.log('Error: ', response.body.error);
            }
            if (callback) {
                callback();
            }
        });
    }, 1000);
}

function doSubscribeRequest() {
    request({
            method: 'POST',
            uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
        },
        (error, response, body) => {
            if (error) {
                console.error('Error while subscription: ', error);
            } else {
                console.log('Subscription result: ', response.body);
            }
        });
}

function isDefined(obj) {
    if (typeof obj == 'undefined') {
        return false;
    }

    if (!obj) {
        return false;
    }

    return obj != null;
}

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);
        var asd = data;
        //console.log(asd);
        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        //console.log(event);
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.post('/webhook_apiai/', (req, res) => {

    //var weather_query = new Boolean(false);
    var weather_query = false;

    const generic_message = {
        attachment: {
            type: "template",
            payload: {
                template_type: "generic",
                elements: [{
                    title: "HAWAIIAN CHICKEN",
                    subtitle: "Chicken meat, juicy pineapples and Mozzarella cheese on tomato pizza sauce.",
                    item_url: "https://en.wikipedia.org/wiki/Pizza",
                    image_url: "http://www.phdelivery.com.my/i/menu/pizza/pizza_hawaiianchicken.jpg",
                    buttons: [/*{
                        type: "web_url",
                        url: "https://en.wikipedia.org/wiki/Pizza",
                        title: "Open Web URL"
                    },*/ {
                     type: "postback",
                     title: "Show HAWAIIAN CHICKEN",
                     payload: "HAWAIIAN CHICKEN"
                     }]
                }, {
                    title: "CHICKEN PEPPERONI",
                    subtitle: "Chicken pepperoni topped with mozzarella cheese and tomato pizza sauce.",
                    item_url: "https://en.wikipedia.org/wiki/Pizza",
                    image_url: "http://www.phdelivery.com.my/i/menu/pizza/pizza_chickenpepperoni.jpg",
                    buttons: [/*{
                     type: "web_url",
                     url: "https://en.wikipedia.org/wiki/Pizza",
                     title: "Open Web URL"
                     },*/ {
                        type: "postback",
                        title: "Show CHICKEN PEPPERONI",
                        payload: "CHICKEN PEPPERONI"
                    }]
                }, {
                    title: "TROPICAL CHICKEN",
                    subtitle: "Sliced chicken rolls and pineapples accompanied by tomato pizza sauce.",
                    item_url: "https://en.wikipedia.org/wiki/Pizza",
                    image_url: "http://www.phdelivery.com.my/i/menu/pizza/pizza_tropicalchicken.jpg",
                    buttons: [/*{
                     type: "web_url",
                     url: "https://en.wikipedia.org/wiki/Pizza",
                     title: "Open Web URL"
                     },*/ {
                        type: "postback",
                        title: "Show TROPICAL CHICKEN",
                        payload: "TROPICAL CHICKEN"
                    }]
                }, {
                    title: "SPICY TUNA",
                    subtitle: "Tuna and onion on a sambal sauce.",
                    item_url: "https://en.wikipedia.org/wiki/Pizza",
                    image_url: "http://www.phdelivery.com.my/i/menu/pizza/pizza_spicytuna.jpg",
                    buttons: [/*{
                     type: "web_url",
                     url: "https://en.wikipedia.org/wiki/Pizza",
                     title: "Open Web URL"
                     },*/ {
                        type: "postback",
                        title: "Show SPICY TUNA",
                        payload: "SPICY TUNA"
                    }]
                }]
            }
        }
    };

    try {
        var data = JSONbig.parse(req.body);
        //console.log(data);
        switch(data.result.action){
            /*case 'show_prod':
                console.log('pizza');
                switch(data.result.parameters.pizza_type){
                    case 'Margherita':
                        generic_message.attachment.payload.elements[0].title = data.result.parameters.pizza_type;
                        generic_message.attachment.payload.elements[0].image_url = "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg";
                }
                break;*/
            case 'show_weather':
                console.log('weather');

                if(isDefined(data.result.parameters['geo-city']) == true || isDefined(data.result.contexts.parameters['geo-city']) == true){
                    if (isDefined(data.result.parameters['geo-city']) == true) {
                        var city = data.result.parameters['geo-city'];
                    } else {
                        var city = data.result.parameters.contexts['geo-city'];
                    }
                    var base_url = "https://query.yahooapis.com/v1/public/yql?" + "q=select+%2A+from+weather.forecast+where+woeid+in+%28select+woeid+from+geo.places%281%29+where+text%3D%27"+city+"%27%29" + "&format=json";
                    weather_query = true;
                    request({
                        url: base_url,
                        method: 'GET',
                        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                        contentType :'application/x-www-form-urlencoded',
                    }, function(error, response, body){
                        if(error) {
                            console.log(error);
                            return res.status(400).json({
                                status: "error",
                                error: error
                            });
                        } else {
                            var query = JSON.parse(body).query;
                            var results = query.results;
                            var channel = results.channel;
                            var item = channel.item;
                            var location = channel.location;
                            var units = channel.units;
                            var condition = item.condition;
                            //console.log(channel)
                            var string = "Today in " + location.city + ": " + condition.text + ", the temperature is " + condition.temp + " " + units.temperature
                            generic_message.attachment.payload.elements[0].title = 'Weather in' + city;
                            generic_message.attachment.payload.elements[0].subtitle = string;
                            generic_message.attachment.payload.elements[0].item_url = channel.link;
                            generic_message.attachment.payload.elements[0].image_url = channel.image.url;
                            generic_message.attachment.payload.elements[0].buttons[0].url = channel.link;
                            return res.status(200).json({
                                data: {
                                    facebook: generic_message
                                }
                            });
                        }
                    });
                } else {
                    return res.status(200).json({
                        data: {
                            facebook: {text: 'no city'}
                        }
                    });
                }
                break;
        }
        console.log(weather_query);
        if (weather_query != true) {
            return res.status(200).json({
                data: {
                    facebook: generic_message
                }
            });
        }
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }
});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

doSubscribeRequest();<|endoftext|>


<commit_before>/**
 * List Component: Basic Usage
 */

import React from 'react';
import List from '../List';

export default () => {
  const items = ['Bananas', 'Apples', 'Oranges', 'Kiwis', 'Strawberries'];

  const itemFormatter = (item, i) => (

      {item}

  );

  return (

      List with array of items and itemFormatter (listStyle: default)

      <List
        items={items}
        itemFormatter={itemFormatter}
      />

      List with with no items that has isEmptyMessage prop

      <List isEmptyMessage="No items to show" items={[]} />

  );
};
<commit_msg>Added bullets example<commit_after>/**
 * List Component: Basic Usage
 */

import React from 'react';
import List from '../List';

export default () => {
  const items = ['Bananas', 'Apples', 'Oranges', 'Kiwis', 'Strawberries'];

  const itemFormatter = (item, i) => (

      {item}

  );

  return (

      List with array of items and itemFormatter (listStyle: default)

      <List
        items={items}
        itemFormatter={itemFormatter}
      />

      List with array of items and itemFormatter (listStyle: bullets)

      <List
        listStyle="bullets"
        items={items}
        itemFormatter={itemFormatter}
      />

      List with with no items that has isEmptyMessage prop

      <List isEmptyMessage="No items to show" items={[]} />

  );
};
<|endoftext|>


<commit_before>var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'
module.exports = function (grunt) {
    require('time-grunt')(grunt);
    require('load-grunt-tasks')(grunt);

    var skiplistConfig = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({
        skiplist: skiplistConfig,
        watch: {
            options: {
                spawn: false
            },
            coffee: {
                files: ['<%= skiplist.app %>/scripts/{,*/}*.coffee'],
                tasks: ['coffee:dist']
            },
            coffeeTest: {
                files: ['test/spec/{,*/}*.coffee'],
                tasks: ['coffee:test']
            },
            compass: {
                files: ['<%= skiplist.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass:server']
            }
        },
        connect: {
            options: {
                port: 9000,
                hostname: 'localhost'
            },
            test: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'test')
                        ];
                    }
                }
            }
        },
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '<%= skiplist.dist %>/*',
                        '!<%= skiplist.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://localhost:<%= connect.options.port %>/index.html']
                }
            }
        },
        coffee: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>/scripts',
                    src: '{,*/}*.coffee',
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: 'test/spec',
                    src: '{,*/}*.coffee',
                    dest: '.tmp/spec',
                    ext: '.js'
                }]
            }
        },
        compass: {
            options: {
                sassDir: '<%= skiplist.app %>/styles',
                cssDir: '.tmp/styles',
                generatedImagesDir: '.tmp/images/generated',
                imagesDir: '<%= skiplist.app %>/images',
                javascriptsDir: '<%= skiplist.app %>/scripts',
                fontsDir: '<%= skiplist.app %>/styles/fonts',
                importPath: '<%= skiplist.app %>/bower_components',
                httpImagesPath: '/images',
                httpGeneratedImagesPath: '/images/generated',
                relativeAssets: false
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },
        // not used since Uglify task does concat,
        // but still available if needed
        /*concat: {
            dist: {}
        },*/
        // not enabled since usemin task does concat and uglify
        // check index.html to edit your build targets
        // enable this task if you prefer defining your build targets here
        /*uglify: {
            dist: {}
        },*/
        replace: {
            dist: {
                options: {
                    patterns: [{ json: grunt.file.readJSON('config.json') }]
                },
                files: [{ src: ['<%= skiplist.app %>/initialization.js'],
                          dest: '<%= skiplist.dist %>/scripts/initialization.js'}]
            }
        },
        useminPrepare: {
            options: {
                dest: '<%= skiplist.dist %>'
            },
            html: [
                '<%= skiplist.app %>/popup.html',
                '<%= skiplist.app %>/options.html'
            ]
        },
        usemin: {
            options: {
                dirs: ['<%= skiplist.dist %>']
            },
            html: ['<%= skiplist.dist %>/{,*/}*.html'],
            css: ['<%= skiplist.dist %>/styles/{,*/}*.css']
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>/images',
                    src: '{,*/}*.{png,jpg,jpeg}',
                    dest: '<%= skiplist.dist %>/images'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>/images',
                    src: '{,*/}*.svg',
                    dest: '<%= skiplist.dist %>/images'
                }]
            }
        },
        cssmin: {
            dist: {
                files: {
                    '<%= skiplist.dist %>/styles/main.css': [
                        '.tmp/styles/{,*/}*.css',
                        '<%= skiplist.app %>/styles/{,*/}*.css'
                    ]
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    /*removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    //collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true*/
                },
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>',
                    src: '*.html',
                    dest: '<%= skiplist.dist %>'
                }]
            }
        },
        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%= skiplist.app %>',
                    dest: '<%= skiplist.dist %>',
                    src: [
                        'initialization.js',
                        'scripts/oauth2/**/*',
                        '*.{ico,png,txt}',
                        'images/{,*/}*.{webp,gif}',
                    ]
                }, {
                    expand: true,
                    cwd: '.tmp/images',
                    dest: '<%= skiplist.dist %>/images',
                    src: [
                        'generated/*'
                    ]
                }]
            }
        },
        concurrent: {
            server: [
                'coffee:dist',
                'compass:server'
            ],
            test: [
                'coffee',
                'compass'
            ],
            dist: [
                'coffee',
                'compass:dist',
                'imagemin',
                'svgmin',
                'htmlmin'
            ]
        },
        chromeManifest: {
            dist: {
                options: {
                    buildnumber: true,
                    background: {
                        target:'scripts/background.js'
                    }
                },
                src: '<%= skiplist.app %>',
                dest: '<%= skiplist.dist %>'
            }
        },
        compress: {
            dist: {
                options: {
                    archive: 'package/Skiplist.zip'
                },
                files: [{
                    expand: true,
                    cwd: 'dist/',
                    src: ['**'],
                    dest: ''
                }]
            }
        }
    });

    grunt.registerTask('test', [
        'clean:server',
        'concurrent:test',
        'connect:test',
        'mocha'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'chromeManifest:dist',
        'useminPrepare',
        'concurrent:dist',
        'cssmin',
        'concat',
        //'uglify',
        'copy',
        'replace',
        'usemin',
        'compress'
    ]);

    grunt.registerTask('default', [
        'test',
        'build'
    ]);
};
<commit_msg>Tests passing inside chrome but not phantom, temporarily removing test from grunt build default task.<commit_after>var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'
module.exports = function (grunt) {
    require('time-grunt')(grunt);
    require('load-grunt-tasks')(grunt);

    var skiplistConfig = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({
        skiplist: skiplistConfig,
        watch: {
            options: {
                spawn: false
            },
            coffee: {
                files: ['<%= skiplist.app %>/scripts/{,*/}*.coffee'],
                tasks: ['coffee:dist']
            },
            coffeeTest: {
                files: ['test/spec/{,*/}*.coffee'],
                tasks: ['coffee:test']
            },
            compass: {
                files: ['<%= skiplist.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass:server']
            }
        },
        connect: {
            options: {
                port: 9000,
                hostname: 'localhost'
            },
            test: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'test')
                        ];
                    }
                }
            }
        },
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '<%= skiplist.dist %>/*',
                        '!<%= skiplist.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://localhost:<%= connect.options.port %>/index.html']
                }
            }
        },
        coffee: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>/scripts',
                    src: '{,*/}*.coffee',
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: 'test/spec',
                    src: '{,*/}*.coffee',
                    dest: '.tmp/spec',
                    ext: '.js'
                }]
            }
        },
        compass: {
            options: {
                sassDir: '<%= skiplist.app %>/styles',
                cssDir: '.tmp/styles',
                generatedImagesDir: '.tmp/images/generated',
                imagesDir: '<%= skiplist.app %>/images',
                javascriptsDir: '<%= skiplist.app %>/scripts',
                fontsDir: '<%= skiplist.app %>/styles/fonts',
                importPath: '<%= skiplist.app %>/bower_components',
                httpImagesPath: '/images',
                httpGeneratedImagesPath: '/images/generated',
                relativeAssets: false
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },
        // not used since Uglify task does concat,
        // but still available if needed
        /*concat: {
            dist: {}
        },*/
        // not enabled since usemin task does concat and uglify
        // check index.html to edit your build targets
        // enable this task if you prefer defining your build targets here
        /*uglify: {
            dist: {}
        },*/
        replace: {
            dist: {
                options: {
                    patterns: [{ json: grunt.file.readJSON('config.json') }]
                },
                files: [{ src: ['<%= skiplist.app %>/initialization.js'],
                          dest: '<%= skiplist.dist %>/scripts/initialization.js'}]
            }
        },
        useminPrepare: {
            options: {
                dest: '<%= skiplist.dist %>'
            },
            html: [
                '<%= skiplist.app %>/popup.html',
                '<%= skiplist.app %>/options.html'
            ]
        },
        usemin: {
            options: {
                dirs: ['<%= skiplist.dist %>']
            },
            html: ['<%= skiplist.dist %>/{,*/}*.html'],
            css: ['<%= skiplist.dist %>/styles/{,*/}*.css']
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>/images',
                    src: '{,*/}*.{png,jpg,jpeg}',
                    dest: '<%= skiplist.dist %>/images'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>/images',
                    src: '{,*/}*.svg',
                    dest: '<%= skiplist.dist %>/images'
                }]
            }
        },
        cssmin: {
            dist: {
                files: {
                    '<%= skiplist.dist %>/styles/main.css': [
                        '.tmp/styles/{,*/}*.css',
                        '<%= skiplist.app %>/styles/{,*/}*.css'
                    ]
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    /*removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    //collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true*/
                },
                files: [{
                    expand: true,
                    cwd: '<%= skiplist.app %>',
                    src: '*.html',
                    dest: '<%= skiplist.dist %>'
                }]
            }
        },
        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%= skiplist.app %>',
                    dest: '<%= skiplist.dist %>',
                    src: [
                        'initialization.js',
                        'scripts/oauth2/**/*',
                        '*.{ico,png,txt}',
                        'images/{,*/}*.{webp,gif}',
                    ]
                }, {
                    expand: true,
                    cwd: '.tmp/images',
                    dest: '<%= skiplist.dist %>/images',
                    src: [
                        'generated/*'
                    ]
                }]
            }
        },
        concurrent: {
            server: [
                'coffee:dist',
                'compass:server'
            ],
            test: [
                'coffee',
                'compass'
            ],
            dist: [
                'coffee',
                'compass:dist',
                'imagemin',
                'svgmin',
                'htmlmin'
            ]
        },
        chromeManifest: {
            dist: {
                options: {
                    buildnumber: true,
                    background: {
                        target:'scripts/background.js'
                    }
                },
                src: '<%= skiplist.app %>',
                dest: '<%= skiplist.dist %>'
            }
        },
        compress: {
            dist: {
                options: {
                    archive: 'package/Skiplist.zip'
                },
                files: [{
                    expand: true,
                    cwd: 'dist/',
                    src: ['**'],
                    dest: ''
                }]
            }
        }
    });

    grunt.registerTask('test', [
        'clean:server',
        'concurrent:test',
        'connect:test',
        'mocha'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'chromeManifest:dist',
        'useminPrepare',
        'concurrent:dist',
        'cssmin',
        'concat',
        //'uglify',
        'copy',
        'replace',
        'usemin',
        'compress'
    ]);

    grunt.registerTask('default', [
         // Right now the tests pass in the browser just fine but phantom has
         // trouble with them, until I can get to the bottom of this tests won't
         // be built until the grunt task.
         //'test',
        'build'
    ]);
};
<|endoftext|>


<commit_before>var
	kind = require('enyo/kind'),
	NewDataList = require('enyo/NewDataList');

var
	Spotlight = require('spotlight'),
	VDRSpotlightSupport = require('spotlight/VDRSpotlightSupport');

var
	Scrollable = require('../Scrollable'),
	ScrollControls = require('../ScrollControls');

module.exports = kind({
	name: 'moon.NewDataList',
	kind: NewDataList,
	scrollControls: [{kind: ScrollControls}],
	touch: false,
	mixins: [Scrollable, VDRSpotlightSupport],
	handlers: {
		onSpotlightUp: 'guard5way',
		onSpotlightRight: 'guard5way',
		onSpotlightDown: 'guard5way',
		onSpotlightLeft: 'guard5way',
		onSpotlightFocus: 'avoidScrollOnDefaultFocus'
	},
	guard5way: function (sender, event) {
		var e, l, idx, d2x, row, limitRow;

		if (!Spotlight.Accelerator.isAccelerating()) return false;

		e = event.type;
		l = (e === 'onSpotlightUp' || e === 'onSpotlightLeft') ?
			this.first :
			this._last;

		idx = event.index;
		d2x = this.dim2extent;

		row = Math.floor(idx / d2x);
		limitRow = Math.floor(l / d2x);

		return (row === limitRow);
	},
	/**
	* Scrolls to a list item (specified by index).
	*
	* In addition to the standard scrolling options, you can specify
	* `focus: true` if you want the item you're scrolling to to be
	* Spotlight focused. Currently, setting `focus: true` forces the
	* scroll behavior to be `instant`, meaning that the scroller will
	* jump to the item (with no animation).
	*
	* @see module:enyo/NewDataList~NewDataList.scrollToItem
	* @param {number} index - The (zero-based) index of the item to scroll to
	* @param {Object} opts - Scrolling options (see module:enyo/Scrollable~Scrollable.scrollTo)
	* @public
	*/
	scrollToItem: function(index, opts) {
		var item;

		if (opts && opts.focus) {
			opts.behavior = 'instant';
		}

		NewDataList.prototype.scrollToItem.apply(this, arguments);

		if (opts && opts.focus && !Spotlight.getPointerMode()) {
			item = this.childForIndex(index);
			if (item) Spotlight.spot(item);
		}
	},
	avoidScrollOnDefaultFocus: function (sender, event) {
		var fv;
		// When focusType is 'default', Spotlight is trying to focus
		// a Control in response to something other than a point or a
		// 5-way move. For example, this can happen during
		// initialization, when the pointer is hidden, or when the app
		// regains focus.
		//
		// In the case where a list element is being focused by default,
		// we short-circuit the action because the element is
		// potentially not in view, and focusing would cause it to
		// scroll it into view for no reason apparent to the user.
		// Instead, we just focus the first visible list element.
		if (event.focusType === 'default' && typeof event.index === 'number') {
			fv = this.getFullyVisibleItems()[0] || this.getVisibleItems()[0];
			if (fv) {
				Spotlight.spot(fv);
				return true;
			}
		}
	}
});
<commit_msg>Explain NewDataList.guard5way() logic<commit_after>var
	kind = require('enyo/kind'),
	NewDataList = require('enyo/NewDataList');

var
	Spotlight = require('spotlight'),
	VDRSpotlightSupport = require('spotlight/VDRSpotlightSupport');

var
	Scrollable = require('../Scrollable'),
	ScrollControls = require('../ScrollControls');

module.exports = kind({
	name: 'moon.NewDataList',
	kind: NewDataList,
	scrollControls: [{kind: ScrollControls}],
	touch: false,
	mixins: [Scrollable, VDRSpotlightSupport],
	handlers: {
		onSpotlightUp: 'guard5way',
		onSpotlightRight: 'guard5way',
		onSpotlightDown: 'guard5way',
		onSpotlightLeft: 'guard5way',
		onSpotlightFocus: 'avoidScrollOnDefaultFocus'
	},
	/**
	* Prevent accelerating 5-way focus events from trying
	* to spot elements that don't yet have DOM nodes.
	*
	* @private
	*/
	guard5way: function (sender, event) {
		var e, l, idx, d2x, row, limitRow;

		// Only need to guard if we are accelerating
		if (!Spotlight.Accelerator.isAccelerating()) return false;

		e = event.type;

		// Figure out the index of the last de-virtualized element
		// in the direction we're currently scrolling; anything
		// further out doesn't yet have a DOM node
		l = (e === 'onSpotlightUp' || e === 'onSpotlightLeft') ?
			this.first :
			this._last;

		// Get the index of the currently focused element
		idx = event.index;

		// In case we're in a grid layout, we need to translate
		// these indices into the corresponding row / column
		d2x = this.dim2extent;
		row = Math.floor(idx / d2x);
		limitRow = Math.floor(l / d2x);

		// If we're in the last de-virtualized row, the element we
		// would focus from here doesn't yet have a DOM node, so
		// we return true to prevent an attempt to focus. We'll wait
		// for the next accelerated 5-way event and try again...
		return (row === limitRow);
	},
	/**
	* Scrolls to a list item (specified by index).
	*
	* In addition to the standard scrolling options, you can specify
	* `focus: true` if you want the item you're scrolling to to be
	* Spotlight focused. Currently, setting `focus: true` forces the
	* scroll behavior to be `instant`, meaning that the scroller will
	* jump to the item (with no animation).
	*
	* @see module:enyo/NewDataList~NewDataList.scrollToItem
	* @param {number} index - The (zero-based) index of the item to scroll to
	* @param {Object} opts - Scrolling options (see module:enyo/Scrollable~Scrollable.scrollTo)
	* @public
	*/
	scrollToItem: function(index, opts) {
		var item;

		if (opts && opts.focus) {
			opts.behavior = 'instant';
		}

		NewDataList.prototype.scrollToItem.apply(this, arguments);

		if (opts && opts.focus && !Spotlight.getPointerMode()) {
			item = this.childForIndex(index);
			if (item) Spotlight.spot(item);
		}
	},
	avoidScrollOnDefaultFocus: function (sender, event) {
		var fv;
		// When focusType is 'default', Spotlight is trying to focus
		// a Control in response to something other than a point or a
		// 5-way move. For example, this can happen during
		// initialization, when the pointer is hidden, or when the app
		// regains focus.
		//
		// In the case where a list element is being focused by default,
		// we short-circuit the action because the element is
		// potentially not in view, and focusing would cause it to
		// scroll it into view for no reason apparent to the user.
		// Instead, we just focus the first visible list element.
		if (event.focusType === 'default' && typeof event.index === 'number') {
			fv = this.getFullyVisibleItems()[0] || this.getVisibleItems()[0];
			if (fv) {
				Spotlight.spot(fv);
				return true;
			}
		}
	}
});
<|endoftext|>


<commit_before>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Renderer/Elements.js
 */

/**
 * Class: OpenLayers.Renderer.VML
 * Render vector features in browsers with VML capability.  Construct a new
 * VML renderer with the <OpenLayers.Renderer.VML> constructor.
 *
 * Note that for all calculations in this class, we use toFixed() to round a
 * float value to an integer. This is done because it seems that VML doesn't
 * support float values.
 *
 * Inherits from:
 *  - <OpenLayers.Renderer.Elements>
 */
OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {

    /**
     * Property: xmlns
     * {String} XML Namespace URN
     */
    xmlns: "urn:schemas-microsoft-com:vml",

    /**
     * Constructor: OpenLayers.Renderer.VML
     * Create a new VML renderer.
     *
     * Parameters:
     * containerID - {String} The id for the element that contains the renderer
     */
    initialize: function(containerID) {
        if (!this.supported()) {
            return;
        }
        if (!document.namespaces.v) {
            document.namespaces.add("v", this.xmlns);
            var style = document.createStyleSheet();
            style.addRule('v\\:*', "behavior: url(#default#VML); " +
                                   "position: absolute; display: inline-block;");
        }
        OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
                                                                arguments);
    },

    /**
     * APIMethod: destroy
     * Deconstruct the renderer.
     */
    destroy: function() {
        OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
    },

    /**
     * APIMethod: supported
     * Determine whether a browser supports this renderer.
     *
     * Returns:
     * {Boolean} The browser supports the VML renderer
     */
    supported: function() {
        return !!(document.namespaces);
    },

    /**
     * Method: setExtent
     * Set the renderer's extent
     *
     * Parameters:
     * extent - {<OpenLayers.Bounds>}
     */
    setExtent: function(extent) {
        OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,
                                                               arguments);
        var resolution = this.getResolution();

        var org = extent.left/resolution + " " +
                    extent.top/resolution;
        this.root.setAttribute("coordorigin", org);

        var size = extent.getWidth()/resolution + " " +
                    -extent.getHeight()/resolution;
        this.root.setAttribute("coordsize", size);
    },

    /**
     * Method: setSize
     * Set the size of the drawing surface
     *
     * Parameters:
     * size - {<OpenLayers.Size>} the size of the drawing surface
     */
    setSize: function(size) {
        OpenLayers.Renderer.prototype.setSize.apply(this, arguments);

        this.rendererRoot.style.width = this.size.w;
        this.rendererRoot.style.height = this.size.h;

        this.root.style.width = this.size.w;
        this.root.style.height = this.size.h;
    },

    /**
     * Method: getNodeType
     * Get the node type for a geometry and style
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     * style - {Object}
     *
     * Returns:
     * {String} The corresponding node type for the specified geometry
     */
    getNodeType: function(geometry, style) {
        var nodeType = null;
        switch (geometry.CLASS_NAME) {
            case "OpenLayers.Geometry.Point":
                nodeType = style.externalGraphic ? "v:rect" : "v:oval";
                break;
            case "OpenLayers.Geometry.Rectangle":
                nodeType = "v:rect";
                break;
            case "OpenLayers.Geometry.LineString":
            case "OpenLayers.Geometry.LinearRing":
            case "OpenLayers.Geometry.Polygon":
            case "OpenLayers.Geometry.Curve":
            case "OpenLayers.Geometry.Surface":
                nodeType = "v:shape";
                break;
            default:
                break;
        }
        return nodeType;
    },

    /**
     * Method: setStyle
     * Use to set all the style attributes to a VML node.
     *
     * Parameters:
     * node - {DOMElement} An VML element to decorate
     * style - {Object}
     * options - {Object} Currently supported options include
     *                              'isFilled' {Boolean} and
     *                              'isStroked' {Boolean}
     * geometry - {<OpenLayers.Geometry>}
     */
    setStyle: function(node, style, options, geometry) {
        style = style  || node._style;
        options = options || node._options;

        if (node._geometryClass == "OpenLayers.Geometry.Point") {
            if (style.externalGraphic) {
                var width = style.graphicWidth || style.graphicHeight;
                var height = style.graphicHeight || style.graphicWidth;
                width = width ? width : style.pointRadius*2;
                height = height ? height : style.pointRadius*2;

                var resolution = this.getResolution();
                var xOffset = (style.graphicXOffset != undefined) ?
                    style.graphicXOffset : -(0.5 * width);
                var yOffset = (style.graphicYOffset != undefined) ?
                    style.graphicYOffset : -(0.5 * height);

                node.style.left = ((geometry.x/resolution)+xOffset).toFixed();
                node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed();
                node.style.width = width;
                node.style.height = height;

                // modify style/options for fill and stroke styling below
                style.fillColor = "none";
                options.isStroked = false;

            } else {
                this.drawCircle(node, geometry, style.pointRadius);
            }
        }

        // fill
        if (options.isFilled) {
            node.setAttribute("fillcolor", style.fillColor);
        } else {
            node.setAttribute("filled", "false");
        }
        var fills = node.getElementsByTagName("fill");
        var fill = (fills.length == 0) ? null : fills[0];
        if (!options.isFilled) {
            if (fill) {
                node.removeChild(fill);
            }
        } else {
            if (!fill) {
                fill = this.createNode('v:fill', node.id + "_fill");

                if (style.fillOpacity) {
                    fill.setAttribute("opacity", style.fillOpacity);
                }

                if (node._geometryClass == "OpenLayers.Geometry.Point" &&
                        style.externalGraphic) {

                    // override fillOpacity
                    if (style.graphicOpacity) {
                        fill.setAttribute("opacity", style.graphicOpacity);
                    }

                    fill.setAttribute("src", style.externalGraphic);
                    fill.setAttribute("type", "frame");
                    node.style.flip = "y";

                    if (!(style.graphicWidth && style.graphicHeight)) {
                      fill.aspect = "atmost";
                    }
                }
                node.appendChild(fill);
            }
        }

        // stroke
        if (options.isStroked) {
            node.setAttribute("strokecolor", style.strokeColor);
            node.setAttribute("strokeweight", style.strokeWidth);
        } else {
            node.setAttribute("stroked", "false");
        }
        var strokes = node.getElementsByTagName("stroke");
        var stroke = (strokes.length == 0) ? null : strokes[0];
        if (!options.isStroked) {
            if (stroke) {
                node.removeChild(stroke);
            }
        } else {
            if (!stroke) {
                stroke = this.createNode('v:stroke', node.id + "_stroke");
                node.appendChild(stroke);
            }
            if (style.strokeOpacity) {
                stroke.setAttribute("opacity", style.strokeOpacity);
            }
            stroke.setAttribute("endcap", !style.strokeLinecap || style.strokeLinecap == 'butt' ? 'flat' : style.strokeLinecap);
        }

        if (style.cursor != null) {
            node.style.cursor = style.cursor;
        }
        return node;
    },

    /**
     * Method: postDraw
     * Some versions of Internet Explorer seem to be unable to set fillcolor
     * and strokecolor to "none" correctly before the fill node is appended to
     * a visible vml node. This method takes care of that and sets fillcolor
     * and strokecolor again if needed.
     *
     * Parameters:
     * node - {DOMElement}
     */
    postDraw: function(node) {
        var fillColor = node._style.fillColor;
        var strokeColor = node._style.strokeColor;
        if (fillColor == "none" &&
                node.getAttribute("fillcolor") != fillColor) {
            node.setAttribute("fillcolor", fillColor);
        }
        if (strokeColor == "none" &&
                node.getAttribute("strokecolor") != strokeColor) {
            node.setAttribute("strokecolor", strokeColor)
        }
    },

    /**
     * Method: setNodeDimension
     * Get the geometry's bounds, convert it to our vml coordinate system,
     * then set the node's position, size, and local coordinate system.
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    setNodeDimension: function(node, geometry) {

        var bbox = geometry.getBounds();
        if(bbox) {
            var resolution = this.getResolution();

            var scaledBox =
                new OpenLayers.Bounds((bbox.left/resolution).toFixed(),
                                      (bbox.bottom/resolution).toFixed(),
                                      (bbox.right/resolution).toFixed(),
                                      (bbox.top/resolution).toFixed());

            // Set the internal coordinate system to draw the path
            node.style.left = scaledBox.left;
            node.style.top = scaledBox.top;
            node.style.width = scaledBox.getWidth();
            node.style.height = scaledBox.getHeight();

            node.coordorigin = scaledBox.left + " " + scaledBox.top;
            node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
        }
    },

    /**
     * Method: createNode
     * Create a new node
     *
     * Parameters:
     * type - {String} Kind of node to draw
     * id - {String} Id for node
     *
     * Returns:
     * {DOMElement} A new node of the given type and id
     */
    createNode: function(type, id) {
        var node = document.createElement(type);
        if (id) {
            node.setAttribute('id', id);
        }
        return node;
    },

    /**
     * Method: nodeTypeCompare
     * Determine whether a node is of a given type
     *
     * Parameters:
     * node - {DOMElement} An VML element
     * type - {String} Kind of node
     *
     * Returns:
     * {Boolean} Whether or not the specified node is of the specified type
     */
    nodeTypeCompare: function(node, type) {

        //split type
        var subType = type;
        var splitIndex = subType.indexOf(":");
        if (splitIndex != -1) {
            subType = subType.substr(splitIndex+1);
        }

        //split nodeName
        var nodeName = node.nodeName;
        splitIndex = nodeName.indexOf(":");
        if (splitIndex != -1) {
            nodeName = nodeName.substr(splitIndex+1);
        }

        return (subType == nodeName);
    },

    /**
     * Method: createRenderRoot
     * Create the renderer root
     *
     * Returns:
     * {DOMElement} The specific render engine's root element
     */
    createRenderRoot: function() {
        return this.nodeFactory(this.container.id + "_vmlRoot", "div");
    },

    /**
     * Method: createRoot
     * Create the main root element
     *
     * Returns:
     * {DOMElement} The main root element to which we'll add vectors
     */
    createRoot: function() {
        return this.nodeFactory(this.container.id + "_root", "v:group");
    },

    /**************************************
     *                                    *
     *     GEOMETRY DRAWING FUNCTIONS     *
     *                                    *
     **************************************/

    /**
     * Method: drawPoint
     * Render a point
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawPoint: function(node, geometry) {
        this.drawCircle(node, geometry, 1);
    },

    /**
     * Method: drawCircle
     * Render a circle.
     * Size and Center a circle given geometry (x,y center) and radius
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     * radius - {float}
     */
    drawCircle: function(node, geometry, radius) {
        if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
            var resolution = this.getResolution();

            node.style.left = (geometry.x /resolution).toFixed() - radius;
            node.style.top = (geometry.y /resolution).toFixed() - radius;

            var diameter = radius * 2;

            node.style.width = diameter;
            node.style.height = diameter;
        }
    },

    /**
     * Method: drawLineString
     * Render a linestring.
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawLineString: function(node, geometry) {
        this.drawLine(node, geometry, false);
    },

    /**
     * Method: drawLinearRing
     * Render a linearring
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawLinearRing: function(node, geometry) {
        this.drawLine(node, geometry, true);
    },

    /**
     * Method: DrawLine
     * Render a line.
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     * closeLine - {Boolean} Close the line? (make it a ring?)
     */
    drawLine: function(node, geometry, closeLine) {

        this.setNodeDimension(node, geometry);

        var resolution = this.getResolution();
        var numComponents = geometry.components.length;
        var parts = new Array(numComponents);

        var comp, x, y;
        for (var i = 0; i < numComponents; i++) {
            comp = geometry.components[i];
            x = (comp.x/resolution);
            y = (comp.y/resolution);
            parts[i] = " " + x.toFixed() + "," + y.toFixed() + " l ";
        }
        var end = (closeLine) ? " x e" : " e";
        node.path = "m" + parts.join("") + end;
    },

    /**
     * Method: drawPolygon
     * Render a polygon
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawPolygon: function(node, geometry) {
        this.setNodeDimension(node, geometry);

        var resolution = this.getResolution();

        var path = [];
        var linearRing, i, comp, x, y;
        for (var j = 0; j < geometry.components.length; j++) {
            linearRing = geometry.components[j];

            path.push("m");
            for (i = 0; i < linearRing.components.length; i++) {
                comp = linearRing.components[i];
                x = comp.x / resolution;
                y = comp.y / resolution;
                path.push(" " + x.toFixed() + "," + y.toFixed());
                if (i==0) {
                    path.push(" l");
                }
            }
            path.push(" x ");
        }
        path.push("e");
        node.path = path.join("");
    },

    /**
     * Method: drawRectangle
     * Render a rectangle
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawRectangle: function(node, geometry) {
        var resolution = this.getResolution();

        node.style.left = geometry.x/resolution;
        node.style.top = geometry.y/resolution;
        node.style.width = geometry.width/resolution;
        node.style.height = geometry.height/resolution;
    },

    /**
     * Method: drawSurface
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawSurface: function(node, geometry) {

        this.setNodeDimension(node, geometry);

        var resolution = this.getResolution();

        var path = [];
        var comp, x, y;
        for (var i = 0; i < geometry.components.length; i++) {
            comp = geometry.components[i];
            x = comp.x / resolution;
            y = comp.y / resolution;
            if ((i%3)==0 && (i/3)==0) {
                path.push("m");
            } else if ((i%3)==1) {
                path.push(" c");
            }
            path.push(" " + x + "," + y);
        }
        path.push(" x e");

        node.path = path.join("");
    },

    CLASS_NAME: "OpenLayers.Renderer.VML"
});
<commit_msg>Cannot switch externalGraphic style on VML-rendered feature. r=tschaub (closes #1312)<commit_after>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Renderer/Elements.js
 */

/**
 * Class: OpenLayers.Renderer.VML
 * Render vector features in browsers with VML capability.  Construct a new
 * VML renderer with the <OpenLayers.Renderer.VML> constructor.
 *
 * Note that for all calculations in this class, we use toFixed() to round a
 * float value to an integer. This is done because it seems that VML doesn't
 * support float values.
 *
 * Inherits from:
 *  - <OpenLayers.Renderer.Elements>
 */
OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {

    /**
     * Property: xmlns
     * {String} XML Namespace URN
     */
    xmlns: "urn:schemas-microsoft-com:vml",

    /**
     * Constructor: OpenLayers.Renderer.VML
     * Create a new VML renderer.
     *
     * Parameters:
     * containerID - {String} The id for the element that contains the renderer
     */
    initialize: function(containerID) {
        if (!this.supported()) {
            return;
        }
        if (!document.namespaces.v) {
            document.namespaces.add("v", this.xmlns);
            var style = document.createStyleSheet();
            style.addRule('v\\:*', "behavior: url(#default#VML); " +
                                   "position: absolute; display: inline-block;");
        }
        OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
                                                                arguments);
    },

    /**
     * APIMethod: destroy
     * Deconstruct the renderer.
     */
    destroy: function() {
        OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
    },

    /**
     * APIMethod: supported
     * Determine whether a browser supports this renderer.
     *
     * Returns:
     * {Boolean} The browser supports the VML renderer
     */
    supported: function() {
        return !!(document.namespaces);
    },

    /**
     * Method: setExtent
     * Set the renderer's extent
     *
     * Parameters:
     * extent - {<OpenLayers.Bounds>}
     */
    setExtent: function(extent) {
        OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,
                                                               arguments);
        var resolution = this.getResolution();

        var org = extent.left/resolution + " " +
                    extent.top/resolution;
        this.root.setAttribute("coordorigin", org);

        var size = extent.getWidth()/resolution + " " +
                    -extent.getHeight()/resolution;
        this.root.setAttribute("coordsize", size);
    },

    /**
     * Method: setSize
     * Set the size of the drawing surface
     *
     * Parameters:
     * size - {<OpenLayers.Size>} the size of the drawing surface
     */
    setSize: function(size) {
        OpenLayers.Renderer.prototype.setSize.apply(this, arguments);

        this.rendererRoot.style.width = this.size.w;
        this.rendererRoot.style.height = this.size.h;

        this.root.style.width = this.size.w;
        this.root.style.height = this.size.h;
    },

    /**
     * Method: getNodeType
     * Get the node type for a geometry and style
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     * style - {Object}
     *
     * Returns:
     * {String} The corresponding node type for the specified geometry
     */
    getNodeType: function(geometry, style) {
        var nodeType = null;
        switch (geometry.CLASS_NAME) {
            case "OpenLayers.Geometry.Point":
                nodeType = style.externalGraphic ? "v:rect" : "v:oval";
                break;
            case "OpenLayers.Geometry.Rectangle":
                nodeType = "v:rect";
                break;
            case "OpenLayers.Geometry.LineString":
            case "OpenLayers.Geometry.LinearRing":
            case "OpenLayers.Geometry.Polygon":
            case "OpenLayers.Geometry.Curve":
            case "OpenLayers.Geometry.Surface":
                nodeType = "v:shape";
                break;
            default:
                break;
        }
        return nodeType;
    },

    /**
     * Method: setStyle
     * Use to set all the style attributes to a VML node.
     *
     * Parameters:
     * node - {DOMElement} An VML element to decorate
     * style - {Object}
     * options - {Object} Currently supported options include
     *                              'isFilled' {Boolean} and
     *                              'isStroked' {Boolean}
     * geometry - {<OpenLayers.Geometry>}
     */
    setStyle: function(node, style, options, geometry) {
        style = style  || node._style;
        options = options || node._options;

        if (node._geometryClass == "OpenLayers.Geometry.Point") {
            if (style.externalGraphic) {
                var width = style.graphicWidth || style.graphicHeight;
                var height = style.graphicHeight || style.graphicWidth;
                width = width ? width : style.pointRadius*2;
                height = height ? height : style.pointRadius*2;

                var resolution = this.getResolution();
                var xOffset = (style.graphicXOffset != undefined) ?
                    style.graphicXOffset : -(0.5 * width);
                var yOffset = (style.graphicYOffset != undefined) ?
                    style.graphicYOffset : -(0.5 * height);

                node.style.left = ((geometry.x/resolution)+xOffset).toFixed();
                node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed();
                node.style.width = width;
                node.style.height = height;

                // modify style/options for fill and stroke styling below
                style.fillColor = "none";
                options.isStroked = false;

            } else {
                this.drawCircle(node, geometry, style.pointRadius);
            }
        }

        // fill
        if (options.isFilled) {
            node.setAttribute("fillcolor", style.fillColor);
        } else {
            node.setAttribute("filled", "false");
        }
        var fills = node.getElementsByTagName("fill");
        var fill = (fills.length == 0) ? null : fills[0];
        if (!options.isFilled) {
            if (fill) {
                node.removeChild(fill);
            }
        } else {
            if (!fill) {
                fill = this.createNode('v:fill', node.id + "_fill");
            }

            if (style.fillOpacity) {
                fill.setAttribute("opacity", style.fillOpacity);
            }

            if (node._geometryClass == "OpenLayers.Geometry.Point" &&
                    style.externalGraphic) {

                // override fillOpacity
                if (style.graphicOpacity) {
                    fill.setAttribute("opacity", style.graphicOpacity);
                }

                fill.setAttribute("src", style.externalGraphic);
                fill.setAttribute("type", "frame");
                node.style.flip = "y";

                if (!(style.graphicWidth && style.graphicHeight)) {
                  fill.aspect = "atmost";
                }

                if (fill.parentNode != node) {
                    node.appendChild(fill);
                }
            }
        }

        // stroke
        if (options.isStroked) {
            node.setAttribute("strokecolor", style.strokeColor);
            node.setAttribute("strokeweight", style.strokeWidth);
        } else {
            node.setAttribute("stroked", "false");
        }
        var strokes = node.getElementsByTagName("stroke");
        var stroke = (strokes.length == 0) ? null : strokes[0];
        if (!options.isStroked) {
            if (stroke) {
                node.removeChild(stroke);
            }
        } else {
            if (!stroke) {
                stroke = this.createNode('v:stroke', node.id + "_stroke");
                node.appendChild(stroke);
            }
            if (style.strokeOpacity) {
                stroke.setAttribute("opacity", style.strokeOpacity);
            }
            stroke.setAttribute("endcap", !style.strokeLinecap || style.strokeLinecap == 'butt' ? 'flat' : style.strokeLinecap);
        }

        if (style.cursor != null) {
            node.style.cursor = style.cursor;
        }
        return node;
    },

    /**
     * Method: postDraw
     * Some versions of Internet Explorer seem to be unable to set fillcolor
     * and strokecolor to "none" correctly before the fill node is appended to
     * a visible vml node. This method takes care of that and sets fillcolor
     * and strokecolor again if needed.
     *
     * Parameters:
     * node - {DOMElement}
     */
    postDraw: function(node) {
        var fillColor = node._style.fillColor;
        var strokeColor = node._style.strokeColor;
        if (fillColor == "none" &&
                node.getAttribute("fillcolor") != fillColor) {
            node.setAttribute("fillcolor", fillColor);
        }
        if (strokeColor == "none" &&
                node.getAttribute("strokecolor") != strokeColor) {
            node.setAttribute("strokecolor", strokeColor)
        }
    },

    /**
     * Method: setNodeDimension
     * Get the geometry's bounds, convert it to our vml coordinate system,
     * then set the node's position, size, and local coordinate system.
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    setNodeDimension: function(node, geometry) {

        var bbox = geometry.getBounds();
        if(bbox) {
            var resolution = this.getResolution();

            var scaledBox =
                new OpenLayers.Bounds((bbox.left/resolution).toFixed(),
                                      (bbox.bottom/resolution).toFixed(),
                                      (bbox.right/resolution).toFixed(),
                                      (bbox.top/resolution).toFixed());

            // Set the internal coordinate system to draw the path
            node.style.left = scaledBox.left;
            node.style.top = scaledBox.top;
            node.style.width = scaledBox.getWidth();
            node.style.height = scaledBox.getHeight();

            node.coordorigin = scaledBox.left + " " + scaledBox.top;
            node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
        }
    },

    /**
     * Method: createNode
     * Create a new node
     *
     * Parameters:
     * type - {String} Kind of node to draw
     * id - {String} Id for node
     *
     * Returns:
     * {DOMElement} A new node of the given type and id
     */
    createNode: function(type, id) {
        var node = document.createElement(type);
        if (id) {
            node.setAttribute('id', id);
        }
        return node;
    },

    /**
     * Method: nodeTypeCompare
     * Determine whether a node is of a given type
     *
     * Parameters:
     * node - {DOMElement} An VML element
     * type - {String} Kind of node
     *
     * Returns:
     * {Boolean} Whether or not the specified node is of the specified type
     */
    nodeTypeCompare: function(node, type) {

        //split type
        var subType = type;
        var splitIndex = subType.indexOf(":");
        if (splitIndex != -1) {
            subType = subType.substr(splitIndex+1);
        }

        //split nodeName
        var nodeName = node.nodeName;
        splitIndex = nodeName.indexOf(":");
        if (splitIndex != -1) {
            nodeName = nodeName.substr(splitIndex+1);
        }

        return (subType == nodeName);
    },

    /**
     * Method: createRenderRoot
     * Create the renderer root
     *
     * Returns:
     * {DOMElement} The specific render engine's root element
     */
    createRenderRoot: function() {
        return this.nodeFactory(this.container.id + "_vmlRoot", "div");
    },

    /**
     * Method: createRoot
     * Create the main root element
     *
     * Returns:
     * {DOMElement} The main root element to which we'll add vectors
     */
    createRoot: function() {
        return this.nodeFactory(this.container.id + "_root", "v:group");
    },

    /**************************************
     *                                    *
     *     GEOMETRY DRAWING FUNCTIONS     *
     *                                    *
     **************************************/

    /**
     * Method: drawPoint
     * Render a point
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawPoint: function(node, geometry) {
        this.drawCircle(node, geometry, 1);
    },

    /**
     * Method: drawCircle
     * Render a circle.
     * Size and Center a circle given geometry (x,y center) and radius
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     * radius - {float}
     */
    drawCircle: function(node, geometry, radius) {
        if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
            var resolution = this.getResolution();

            node.style.left = (geometry.x /resolution).toFixed() - radius;
            node.style.top = (geometry.y /resolution).toFixed() - radius;

            var diameter = radius * 2;

            node.style.width = diameter;
            node.style.height = diameter;
        }
    },

    /**
     * Method: drawLineString
     * Render a linestring.
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawLineString: function(node, geometry) {
        this.drawLine(node, geometry, false);
    },

    /**
     * Method: drawLinearRing
     * Render a linearring
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawLinearRing: function(node, geometry) {
        this.drawLine(node, geometry, true);
    },

    /**
     * Method: DrawLine
     * Render a line.
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     * closeLine - {Boolean} Close the line? (make it a ring?)
     */
    drawLine: function(node, geometry, closeLine) {

        this.setNodeDimension(node, geometry);

        var resolution = this.getResolution();
        var numComponents = geometry.components.length;
        var parts = new Array(numComponents);

        var comp, x, y;
        for (var i = 0; i < numComponents; i++) {
            comp = geometry.components[i];
            x = (comp.x/resolution);
            y = (comp.y/resolution);
            parts[i] = " " + x.toFixed() + "," + y.toFixed() + " l ";
        }
        var end = (closeLine) ? " x e" : " e";
        node.path = "m" + parts.join("") + end;
    },

    /**
     * Method: drawPolygon
     * Render a polygon
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawPolygon: function(node, geometry) {
        this.setNodeDimension(node, geometry);

        var resolution = this.getResolution();

        var path = [];
        var linearRing, i, comp, x, y;
        for (var j = 0; j < geometry.components.length; j++) {
            linearRing = geometry.components[j];

            path.push("m");
            for (i = 0; i < linearRing.components.length; i++) {
                comp = linearRing.components[i];
                x = comp.x / resolution;
                y = comp.y / resolution;
                path.push(" " + x.toFixed() + "," + y.toFixed());
                if (i==0) {
                    path.push(" l");
                }
            }
            path.push(" x ");
        }
        path.push("e");
        node.path = path.join("");
    },

    /**
     * Method: drawRectangle
     * Render a rectangle
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawRectangle: function(node, geometry) {
        var resolution = this.getResolution();

        node.style.left = geometry.x/resolution;
        node.style.top = geometry.y/resolution;
        node.style.width = geometry.width/resolution;
        node.style.height = geometry.height/resolution;
    },

    /**
     * Method: drawSurface
     *
     * Parameters:
     * node - {DOMElement}
     * geometry - {<OpenLayers.Geometry>}
     */
    drawSurface: function(node, geometry) {

        this.setNodeDimension(node, geometry);

        var resolution = this.getResolution();

        var path = [];
        var comp, x, y;
        for (var i = 0; i < geometry.components.length; i++) {
            comp = geometry.components[i];
            x = comp.x / resolution;
            y = comp.y / resolution;
            if ((i%3)==0 && (i/3)==0) {
                path.push("m");
            } else if ((i%3)==1) {
                path.push(" c");
            }
            path.push(" " + x + "," + y);
        }
        path.push(" x e");

        node.path = path.join("");
    },

    CLASS_NAME: "OpenLayers.Renderer.VML"
});
<|endoftext|>