<commit_before>/**
 * Created by zzn on 2015-05-05.
 * Contains the MVC framework working for a PostItem
 *
 * Requires jQuery
 */

var PostItemMVC = (function ($, wx, zHelper) {
    "use strict";
    var CONST_SPREADSHEET_URL = "https://spreadsheets.google.com/feeds/list/1vzugrYLpgXmFODqysSx331Lhq8LpDQGJ4sQtwSMrtV4/1/public/values?alt=json";
    var CONST_FIELD_KEYS = {
        xq: "gsx$需求类别",
        qssj: "gsx$预计起始日期超过这个日期本条信息失效",
        qy: "gsx$区域",
        ybhzcs: "gsx$邮编或者城市",
        yqjg: "gsx$预期价格美元每月",
        grjs: "gsx$租房需求介绍和自我介绍",
        wxId: "gsx$微信号",
        xb: "gsx$性别",
        dhyx: "gsx$其他联系方式电话邮箱等",
        fbsj: "gsx$timestamp",
        ltst: "gsx$shorttermorlongterm",
        entryid: "gsx$entryid"
    };

    var CONST_FIELDS = [
        'xq', 'qssj', 'qy', 'ybhzcs', 'yqjg', 'grjs',
        'wxId', 'xb', 'dhyx', 'fbsj', 'ltst'
    ];

    var CONST_VALUE_ID_PREFIX = "#value_";
    var CONST_ITEM_TEMPLATE_ID = "#value_panel_template";
    var CONST_DOMAIN_NAME = "rent.zzn.im";
    /**
     * Class definition of Model
     * @constructor
     */
    var Model = function () {
        this.clearData(); // init with clear data
    };

    /**
     *
     * Creates a view as a clone of template
     * @constructor
     */
    var ItemView = function (guid) {
        this.guid_ = guid;
        this.templateClone_ = $(CONST_ITEM_TEMPLATE_ID).clone();
        this.guidLink_ = "http://" + CONST_DOMAIN_NAME + "/?" + $.param({"guid": guid});
    };

    var PanelView = function () {
        this.panelDom_ = $("#value_panel_parent");
    };

    /**
     * Class definition of Controller
     * @constructor
     */
    var Controller = function () {
        this.model_ = new Model();
        this.panelView_ = new PanelView();
    };

    // Set up cors proxy for jQuery
    $.ajaxPrefilter(function (options) {
        if (options.crossDomain && jQuery.support.cors) {
            var http = (window.location.protocol === 'http:' ? 'http:' : 'https:');
            options.url = http + '//cors-anywhere.herokuapp.com/' + options.url;
        }
    });

    Model.prototype.loadDataAsync = function (callback) {
        var that = this;
        $.get(CONST_SPREADSHEET_URL, function (data) {
            zHelper.log("loaded data from CORS proxy");
            that.cachedData_ = {}; //update data;
            data.feed.entry.forEach(function (row) {
                var guid = row[CONST_FIELD_KEYS.entryid]["$t"] || "N/A";
                var datum = {};
                CONST_FIELDS.forEach(function (fieldName) {
                    datum[fieldName] = row[CONST_FIELD_KEYS[fieldName]]["$t"] || "N/A";
                });
                that.cachedData_[guid] = datum;
                that.guidArray_.push(guid);
            });
            callback(data); // done
        });
    };

    Model.prototype.clearData = function () {
        this.cachedData_ = {};
        this.guidArray_ = [];
    };

    Model.prototype.getGuidArray = function () {
        return this.guidArray_;
    };

    Model.prototype.getFieldData = function (fieldName, guid) {
        return this.cachedData_[guid][fieldName];
    };

    /**
     * Add Guid Related Links
     * @param guid
     * @private
     */
    ItemView.prototype.decrorate_ = function () {
        var guid = this.guid_;
        var template = this.templateClone_;
        var guidLink = this.guidLink_;
        template.find("#qrcode").qrcode(guidLink);
        template.find("#row_link").attr("href", guidLink);
        template.find("#btn_share").attr("data-guid", guid);
        template.find("#btn_like").attr("data-guid", guid);
        template.show();
    };

    /**
     *
     * @param should boolean to indicate show(true) or hide(false)
     */
    ItemView.prototype.shouldShowContactInfo = function (should, model) {
        var that = this;
        var template = this.templateClone_;
        if (should) {
            template.find("#btn_show_wxId").hide();
            template.find("#btn_show_dhyx").hide();
        } else {
            template.find("#value_wxId").hide();
            template.find("#value_dhyx").hide();

            template.find("#value_wxId").hide();
            template.find("#btn_show_wxId").click(function () {
                template.find("#btn_show_wxId").hide();
                template.find("#value_wxId").show();
            });
            template.find("#btn_show_dhyx").click(function () {
                template.find("#btn_show_dhyx").hide();
                template.find("#value_dhyx").show();
            });

        }
    };

    ItemView.prototype.displayData = function (model) {
        var template = this.templateClone_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        if (xq == "出租") {
            template.addClass("panel-success");
        } else if (xq == "求租") {
            template.addClass("panel-primary");
        } else if (xq == "找室友") {
            template.addClass("panel-warning");
        }
        CONST_FIELDS.forEach(function (fieldName) {
            template.find(CONST_VALUE_ID_PREFIX + fieldName).text(model.getFieldData(fieldName, guid));
        });

        // Set up Id of template
        template.attr("id", "value_panel" + guid);
        this.decrorate_();
    };
    ItemView.prototype.getDom = function () {
        return this.templateClone_;
    };

    ItemView.prototype.configureWeChat_ = function(model){
        zHelper.logAlert("clicked configure wechat!");
        var guidLink = this.guidLink_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        var title, link, imgUrl, desc;
        if (xq == "出租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-green-1024sq2.jpg";
        } else if (xq == "求租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-blue-1024sq2.jpg";
        } else if (xq == "找室友") {
            imgUrl = "http://" + CONST_DOMAIN_NAME +"/img/logo-v1-yellow-1024sq2.jpg";
        }
        title = "湾区好室友 - " + model.getFieldData("qy", guid) + xq;
        link = guidLink;
        desc = "时间是" + model.getFieldData("qssj", guid) + "左右开始。\r\n"
            + "情况简介：" + model.getFieldData("grjs", guid);
        var wxData = {
            title: title,
            link: link,
            imgUrl: imgUrl,
            desc: desc,
            trigger: function (res) {
                zHelper.logAlert('用户点击发送给朋友');
            },
            success: function (ret) {
                zHelper.logAlert("shared!");
                zHelper.logAlert("ret=" + JSON.stringify(ret));
            },
            cancel: function (ret) {
                zHelper.logAlert("cancel share!");
                zHelper.logAlert("ret=" + JSON.stringify(ret));
            }
        };
        wx.onMenuShareAppMessage(wxData);
        wx.onMenuShareTimeline(wxData);

    };
    PanelView.prototype.addChild = function (item) {
        this.panelDom_.append(item);
    };

    PanelView.prototype.clearChildren = function () {
        this.panelDom_.empty();
    };

    Controller.prototype.initAsync = function (callback) {
        this.model_.loadDataAsync(callback);
    };

    Controller.prototype.showAllItems = function () {
        var that = this;
        this.panelView_.clearChildren();
        this.model_.getGuidArray().forEach(function (guid) {
            var item = new ItemView(guid);
            item.displayData(that.model_, guid);
            item.shouldShowContactInfo(false, that.model_);
            that.panelView_.addChild(item.getDom());

        });
    };

    /**
     *
     * @param guid The GUID of a given listing post
     */
    Controller.prototype.showItemByGuid = function (guid) {
        this.panelView_.clearChildren();
        var that = this;
        var item = new ItemView(guid);
        item.displayData(this.model_, guid);
        item.shouldShowContactInfo(true, this.model_);

        wx.ready(function(res){
            zHelper.log(res);
            wx.checkJsApi({
                jsApiList: [
                    "onMenuShareTimeline",
                    "onMenuShareAppMessage"
                ],
                success: function (res) {
                    zHelper.logAlert("wxCheckJsApi, success, configuring WeChat");
                    zHelper.log(JSON.stringify(res));
                    // TODO(zzn):
                    // uncomment update it here
                    item.configureWeChat_(that.model_);
                },
                fail: function (res) {
                    zHelper.logAlert("wxCheckJsApi, faile");
                    zHelper.log(JSON.stringify(res));
                }
            });

        });

        zHelper.log("XXX after setting wechat");
        this.panelView_.addChild(item.getDom());
    };

    return {
        Controller: Controller
    };
})($, wx, zHelper);<commit_msg>Update<commit_after>/**
 * Created by zzn on 2015-05-05.
 * Contains the MVC framework working for a PostItem
 *
 * Requires jQuery
 */

var PostItemMVC = (function ($, wx, zHelper) {
    "use strict";
    var CONST_SPREADSHEET_URL = "https://spreadsheets.google.com/feeds/list/1vzugrYLpgXmFODqysSx331Lhq8LpDQGJ4sQtwSMrtV4/1/public/values?alt=json";
    var CONST_FIELD_KEYS = {
        xq: "gsx$需求类别",
        qssj: "gsx$预计起始日期超过这个日期本条信息失效",
        qy: "gsx$区域",
        ybhzcs: "gsx$邮编或者城市",
        yqjg: "gsx$预期价格美元每月",
        grjs: "gsx$租房需求介绍和自我介绍",
        wxId: "gsx$微信号",
        xb: "gsx$性别",
        dhyx: "gsx$其他联系方式电话邮箱等",
        fbsj: "gsx$timestamp",
        ltst: "gsx$shorttermorlongterm",
        entryid: "gsx$entryid"
    };

    var CONST_FIELDS = [
        'xq', 'qssj', 'qy', 'ybhzcs', 'yqjg', 'grjs',
        'wxId', 'xb', 'dhyx', 'fbsj', 'ltst'
    ];

    var CONST_VALUE_ID_PREFIX = "#value_";
    var CONST_ITEM_TEMPLATE_ID = "#value_panel_template";
    var CONST_DOMAIN_NAME = "rent.zzn.im";
    /**
     * Class definition of Model
     * @constructor
     */
    var Model = function () {
        this.clearData(); // init with clear data
    };

    /**
     *
     * Creates a view as a clone of template
     * @constructor
     */
    var ItemView = function (guid) {
        this.guid_ = guid;
        this.templateClone_ = $(CONST_ITEM_TEMPLATE_ID).clone();
        this.guidLink_ = "http://" + CONST_DOMAIN_NAME + "/?" + $.param({"guid": guid});
    };

    var PanelView = function () {
        this.panelDom_ = $("#value_panel_parent");
    };

    /**
     * Class definition of Controller
     * @constructor
     */
    var Controller = function () {
        this.model_ = new Model();
        this.panelView_ = new PanelView();
    };

    // Set up cors proxy for jQuery
    $.ajaxPrefilter(function (options) {
        if (options.crossDomain && jQuery.support.cors) {
            var http = (window.location.protocol === 'http:' ? 'http:' : 'https:');
            options.url = http + '//cors-anywhere.herokuapp.com/' + options.url;
        }
    });

    Model.prototype.loadDataAsync = function (callback) {
        var that = this;
        $.get(CONST_SPREADSHEET_URL, function (data) {
            zHelper.log("loaded data from CORS proxy");
            that.cachedData_ = {}; //update data;
            data.feed.entry.forEach(function (row) {
                var guid = row[CONST_FIELD_KEYS.entryid]["$t"] || "N/A";
                var datum = {};
                CONST_FIELDS.forEach(function (fieldName) {
                    datum[fieldName] = row[CONST_FIELD_KEYS[fieldName]]["$t"] || "N/A";
                });
                that.cachedData_[guid] = datum;
                that.guidArray_.push(guid);
            });
            callback(data); // done
        });
    };

    Model.prototype.clearData = function () {
        this.cachedData_ = {};
        this.guidArray_ = [];
    };

    Model.prototype.getGuidArray = function () {
        return this.guidArray_;
    };

    Model.prototype.getFieldData = function (fieldName, guid) {
        return this.cachedData_[guid][fieldName];
    };

    /**
     * Add Guid Related Links
     * @param guid
     * @private
     */
    ItemView.prototype.decrorate_ = function () {
        var guid = this.guid_;
        var template = this.templateClone_;
        var guidLink = this.guidLink_;
        template.find("#qrcode").qrcode(guidLink);
        template.find("#row_link").attr("href", guidLink);
        template.find("#btn_share").attr("data-guid", guid);
        template.find("#btn_like").attr("data-guid", guid);
        template.show();
    };

    /**
     *
     * @param should boolean to indicate show(true) or hide(false)
     */
    ItemView.prototype.shouldShowContactInfo = function (should, model) {
        var that = this;
        var template = this.templateClone_;
        if (should) {
            template.find("#btn_show_wxId").hide();
            template.find("#btn_show_dhyx").hide();
        } else {
            template.find("#value_wxId").hide();
            template.find("#value_dhyx").hide();

            template.find("#value_wxId").hide();
            template.find("#btn_show_wxId").click(function () {
                template.find("#btn_show_wxId").hide();
                template.find("#value_wxId").show();
            });
            template.find("#btn_show_dhyx").click(function () {
                template.find("#btn_show_dhyx").hide();
                template.find("#value_dhyx").show();
            });

        }
    };

    ItemView.prototype.displayData = function (model) {
        var template = this.templateClone_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        if (xq == "出租") {
            template.addClass("panel-success");
        } else if (xq == "求租") {
            template.addClass("panel-primary");
        } else if (xq == "找室友") {
            template.addClass("panel-warning");
        }
        CONST_FIELDS.forEach(function (fieldName) {
            template.find(CONST_VALUE_ID_PREFIX + fieldName).text(model.getFieldData(fieldName, guid));
        });

        // Set up Id of template
        template.attr("id", "value_panel" + guid);
        this.decrorate_();
    };
    ItemView.prototype.getDom = function () {
        return this.templateClone_;
    };

    ItemView.prototype.configureWeChat_ = function(model){
        zHelper.logAlert("clicked configure wechat!");
        var guidLink = this.guidLink_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        var title, link, imgUrl, desc;
        if (xq == "出租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-green-1024sq2.jpg";
        } else if (xq == "求租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-blue-1024sq2.jpg";
        } else if (xq == "找室友") {
            imgUrl = "http://" + CONST_DOMAIN_NAME +"/img/logo-v1-yellow-1024sq2.jpg";
        }
        title = "湾区好室友 - " + model.getFieldData("qy", guid) + xq;
        link = guidLink;
        desc = model.getFieldData("qssj", guid) + "左右开始。\r\n"
            + "简介：" + model.getFieldData("grjs", guid);
        var wxData = {
            title: title,
            link: link,
            imgUrl: imgUrl,
            desc: desc,
            trigger: function (res) {
                zHelper.logAlert('用户点击发送给朋友');
            },
            success: function (ret) {
                zHelper.logAlert("shared!");
                zHelper.logAlert("ret=" + JSON.stringify(ret));
            },
            cancel: function (ret) {
                zHelper.logAlert("cancel share!");
                zHelper.logAlert("ret=" + JSON.stringify(ret));
            }
        };
        wx.onMenuShareAppMessage(wxData);
        wx.onMenuShareTimeline(wxData);

    };
    PanelView.prototype.addChild = function (item) {
        this.panelDom_.append(item);
    };

    PanelView.prototype.clearChildren = function () {
        this.panelDom_.empty();
    };

    Controller.prototype.initAsync = function (callback) {
        this.model_.loadDataAsync(callback);
    };

    Controller.prototype.showAllItems = function () {
        var that = this;
        this.panelView_.clearChildren();
        this.model_.getGuidArray().forEach(function (guid) {
            var item = new ItemView(guid);
            item.displayData(that.model_, guid);
            item.shouldShowContactInfo(false, that.model_);
            that.panelView_.addChild(item.getDom());

        });
    };

    /**
     *
     * @param guid The GUID of a given listing post
     */
    Controller.prototype.showItemByGuid = function (guid) {
        this.panelView_.clearChildren();
        var that = this;
        var item = new ItemView(guid);
        item.displayData(this.model_, guid);
        item.shouldShowContactInfo(true, this.model_);

        wx.ready(function(res){
            zHelper.log(res);
            wx.checkJsApi({
                jsApiList: [
                    "onMenuShareTimeline",
                    "onMenuShareAppMessage"
                ],
                success: function (res) {
                    zHelper.logAlert("wxCheckJsApi, success, configuring WeChat");
                    zHelper.log(JSON.stringify(res));
                    // TODO(zzn):
                    // uncomment update it here
                    item.configureWeChat_(that.model_);
                },
                fail: function (res) {
                    zHelper.logAlert("wxCheckJsApi, faile");
                    zHelper.log(JSON.stringify(res));
                }
            });

        });

        zHelper.log("XXX after setting wechat");
        this.panelView_.addChild(item.getDom());
    };

    return {
        Controller: Controller
    };
})($, wx, zHelper);<|endoftext|>


<commit_before>'use strict';

var mongoose = require('mongoose');

function MessageManager(options) {
    this.core = options.core;
}

MessageManager.prototype.create = function(options, cb) {
    var Message = mongoose.model('Message'),
        Room = mongoose.model('Room'),
        User = mongoose.model('User');

    Room.findById(options.room, function(err, room) {
        if (err) {
            console.error(err);
            return cb(err);
        }
        if (room.archived) {
            return cb('Room is archived.');
        }
        Message.create(options, function(err, message) {
            if (err) {
                console.error(err);
                return cb(err);
            }
            // Touch Room's lastActive
            room.lastActive = message.posted;
            room.save();
            // Temporary workaround for _id until populate can do aliasing
            User.findOne(message.owner, function(err, user) {
                if (err) {
                    console.error(err);
                    return cb(err);
                }
                message = message.toJSON();
                message.owner = user.toJSON();
                message.room = room.toJSON();
                cb(null, message);
                this.core.emit('messages:new', message);
            }.bind(this));
        }.bind(this));
    }.bind(this));
};

MessageManager.prototype.list = function(options, cb) {
    var Message = mongoose.model('Message'),
        User = mongoose.model('User');

    var find = Message.find({
        room: options.room || null
    });

    if (options.from) {
        find.where('_id').gt(options.from);
    }

    find
        .populate('owner', 'id username displayName email avatar')
        .limit(options.limit || 500)
        .sort({ 'posted': -1 })
        .exec(function(err, messages) {
            if (err) {
                console.error(err);
                return cb(err);
            }
            cb(null, messages.reverse());
        });
};

module.exports = MessageManager;
<commit_msg>Message list method returns room ID and name. Fixes #171<commit_after>'use strict';

var mongoose = require('mongoose');

function MessageManager(options) {
    this.core = options.core;
}

MessageManager.prototype.create = function(options, cb) {
    var Message = mongoose.model('Message'),
        Room = mongoose.model('Room'),
        User = mongoose.model('User');

    Room.findById(options.room, function(err, room) {
        if (err) {
            console.error(err);
            return cb(err);
        }
        if (room.archived) {
            return cb('Room is archived.');
        }
        Message.create(options, function(err, message) {
            if (err) {
                console.error(err);
                return cb(err);
            }
            // Touch Room's lastActive
            room.lastActive = message.posted;
            room.save();
            // Temporary workaround for _id until populate can do aliasing
            User.findOne(message.owner, function(err, user) {
                if (err) {
                    console.error(err);
                    return cb(err);
                }
                message = message.toJSON();
                message.owner = user.toJSON();
                message.room = room.toJSON();
                cb(null, message);
                this.core.emit('messages:new', message);
            }.bind(this));
        }.bind(this));
    }.bind(this));
};

MessageManager.prototype.list = function(options, cb) {
    var Message = mongoose.model('Message'),
        User = mongoose.model('User');

    var find = Message.find({
        room: options.room || null
    });

    if (options.from) {
        find.where('_id').gt(options.from);
    }

    find
        .populate('owner', 'id username displayName email avatar')
        .populate('room', 'id name')
        .limit(options.limit || 500)
        .sort({ 'posted': -1 })
        .exec(function(err, messages) {
            if (err) {
                console.error(err);
                return cb(err);
            }
            cb(null, messages.reverse());
        });
};

module.exports = MessageManager;
<|endoftext|>


<commit_before>/**
 * Created by zzn on 2015-05-05.
 * Contains the MVC framework working for a PostItem
 *
 * Requires jQuery
 */

var PostItemMVC = (function ($, wx, zHelper) {
    "use strict";
    var CONST_SPREADSHEET_URL = "https://spreadsheets.google.com/feeds/list/1vzugrYLpgXmFODqysSx331Lhq8LpDQGJ4sQtwSMrtV4/1/public/values?alt=json";
    var CONST_FIELD_KEYS = {
        xq: "gsx$需求类别",
        qssj: "gsx$预计起始日期超过这个日期本条信息失效",
        qy: "gsx$区域",
        ybhzcs: "gsx$邮编或者城市",
        yqjg: "gsx$预期价格美元每月",
        grjs: "gsx$租房需求介绍和自我介绍",
        wxId: "gsx$微信号",
        xb: "gsx$性别",
        dhyx: "gsx$其他联系方式电话邮箱等",
        fbsj: "gsx$timestamp",
        ltst: "gsx$shorttermorlongterm",
        entryid: "gsx$entryid"
    };

    var CONST_FIELDS = [
        'xq', 'qssj', 'qy', 'ybhzcs', 'yqjg', 'grjs',
        'wxId', 'xb', 'dhyx', 'fbsj', 'ltst'
    ];

    var CONST_VALUE_ID_PREFIX = "#value_";
    var CONST_ITEM_TEMPLATE_ID = "#value_panel_template";
    var CONST_DOMAIN_NAME = "rent.zzn.im";
    /**
     * Class definition of Model
     * @constructor
     */
    var Model = function () {
        this.clearData(); // init with clear data
    };

    /**
     *
     * Creates a view as a clone of template
     * @constructor
     */
    var ItemView = function (guid) {
        this.guid_ = guid;
        this.templateClone_ = $(CONST_ITEM_TEMPLATE_ID).clone();
        this.guidLink_ = "http://" + CONST_DOMAIN_NAME + "/?" + $.param({"guid": guid});
    };

    var PanelView = function () {
        this.panelDom_ = $("#value_panel_parent");
    };

    /**
     * Class definition of Controller
     * @constructor
     */
    var Controller = function () {
        this.model_ = new Model();
        this.panelView_ = new PanelView();
    };

    // Set up cors proxy for jQuery
    $.ajaxPrefilter(function (options) {
        if (options.crossDomain && jQuery.support.cors) {
            var http = (window.location.protocol === 'http:' ? 'http:' : 'https:');
            options.url = http + '//cors-anywhere.herokuapp.com/' + options.url;
        }
    });

    Model.prototype.loadDataAsync = function (callback) {
        var that = this;
        $.get(CONST_SPREADSHEET_URL, function (data) {
            zHelper.log("loaded data from CORS proxy");
            that.cachedData_ = {}; //update data;
            data.feed.entry.forEach(function (row) {
                var guid = row[CONST_FIELD_KEYS.entryid]["$t"] || "N/A";
                var datum = {};
                CONST_FIELDS.forEach(function (fieldName) {
                    datum[fieldName] = row[CONST_FIELD_KEYS[fieldName]]["$t"] || "N/A";
                });
                that.cachedData_[guid] = datum;
                that.guidArray_.push(guid);
            });
            callback(data); // done
        });
    };

    Model.prototype.clearData = function () {
        this.cachedData_ = {};
        this.guidArray_ = [];
    };

    Model.prototype.getGuidArray = function () {
        return this.guidArray_;
    };

    Model.prototype.getFieldData = function (fieldName, guid) {
        return this.cachedData_[guid][fieldName];
    };

    /**
     * Add Guid Related Links
     * @param guid
     * @private
     */
    ItemView.prototype.decrorate_ = function () {
        var guid = this.guid_;
        var template = this.templateClone_;
        var guidLink = this.guidLink_;
        template.find("#qrcode").qrcode(guidLink);
        template.find("#row_link").attr("href", guidLink);
        template.find("#btn_share").attr("data-guid", guid);
        template.find("#btn_like").attr("data-guid", guid);
        template.show();
    };

    /**
     *
     * @param should boolean to indicate show(true) or hide(false)
     */
    ItemView.prototype.shouldShowContactInfo = function (should, model) {
        var template = this.templateClone_;
        if (should) {
            template.find("#btn_show_wxId").hide();
            template.find("#btn_show_dhyx").hide();
        } else {
            template.find("#value_wxId").hide();
            template.find("#value_dhyx").hide();

            template.find("#value_wxId").hide();
            template.find("#btn_show_wxId").click(function () {
                template.find("#btn_show_wxId").hide();
                template.find("#value_wxId").show();
            });
            template.find("#btn_show_dhyx").click(function () {
                template.find("#btn_show_dhyx").hide();
                template.find("#value_dhyx").show();
            });

        }
        //TODO(zzn): update this to somewhere proper
        template.find("#btn_share").click(function(){
            alert("clicked share, setting configureWeChat");
            that.configureWeChat_(model);
        });
    };

    ItemView.prototype.displayData = function (model) {
        var template = this.templateClone_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        if (xq == "出租") {
            template.addClass("panel-success");
        } else if (xq == "求租") {
            template.addClass("panel-primary");
        } else if (xq == "找室友") {
            template.addClass("panel-warning");
        }
        CONST_FIELDS.forEach(function (fieldName) {
            template.find(CONST_VALUE_ID_PREFIX + fieldName).text(model.getFieldData(fieldName, guid));
        });

        // Set up Id of template
        template.attr("id", "value_panel" + guid);
        this.decrorate_();
    };
    ItemView.prototype.getDom = function () {
        return this.templateClone_;
    };

    ItemView.prototype.configureWeChat_ = function(model){
        alert("clicked configure wechat!");
        var guidLink = this.guidLink_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        var title, link, imgUrl, desc;

        if (xq == "出租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-green-1024sq2.jpg";
        } else if (xq == "求租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-blue-1024sq2.jpg";
        } else if (xq == "找室友") {
            imgUrl = "http://" + CONST_DOMAIN_NAME +"/img/logo-v1-yellow-1024sq2.jpg";
        }
        title = model.getFieldData("xq", guid) +
            model.getFieldData("qy", guid) + "，时间是"
        model.getFieldData("qssj", guid) + "左右开始，求分享！";

        link = guidLink;
        desc = model.getFieldData("grjs", guid);
        var wxData = {
            title: title,
            link: link,
            imgUrl: imgUrl,
            desc: desc,
            trigger: function (res) {
                alert('用户点击分享到微博');
            },
            complete: function (res) {
                alert(JSON.stringify(res));
            },
            success: function (res) {
                alert('已分享');
            },
            cancel: function (res) {
                alert('已取消');
            },
            fail: function (res) {
                alert(JSON.stringify(res));
            }
        };
        alert(wxData);
        wx.onMenuShareAppMessage(wxData);

    };
    PanelView.prototype.addChild = function (item) {
        this.panelDom_.append(item);
    };

    PanelView.prototype.clearChildren = function () {
        this.panelDom_.empty();
    };

    Controller.prototype.initAsync = function (callback) {
        this.model_.loadDataAsync(callback);
    };

    Controller.prototype.showAllItems = function () {
        var that = this;
        this.panelView_.clearChildren();
        this.model_.getGuidArray().forEach(function (guid) {
            var item = new ItemView(guid);
            item.displayData(that.model_, guid);
            item.shouldShowContactInfo(false);
            that.panelView_.addChild(item.getDom());

        });
    };

    /**
     *
     * @param guid The GUID of a given listing post
     */
    Controller.prototype.showItemByGuid = function (guid) {
        this.panelView_.clearChildren();
        var that = this;
        var item = new ItemView(guid);
        item.displayData(this.model_, guid);
        item.shouldShowContactInfo(true);

        wx.ready(function(res){
            zHelper.log(res);
            wx.checkJsApi({
                jsApiList: [
                    "onMenuShareTimeline",
                    "onMenuShareAppMessage"
                ],
                success: function (res) {
                    alert("wxCheckJsApi, success");
                    zHelper.log(JSON.stringify(res));
                    // TODO(zzn):
                    // uncomment update it here
                    // item.configureWeChat_(that.model_);
                },
                fail: function (res) {
                    alert("wxCheckJsApi, faile");
                    zHelper.log(JSON.stringify(res));
                }
            });

        });

        zHelper.log("XXX after setting wechat");
        this.panelView_.addChild(item.getDom());
    };

    return {
        Controller: Controller
    };
})($, wx, zHelper);<commit_msg>debug wechat<commit_after>/**
 * Created by zzn on 2015-05-05.
 * Contains the MVC framework working for a PostItem
 *
 * Requires jQuery
 */

var PostItemMVC = (function ($, wx, zHelper) {
    "use strict";
    var CONST_SPREADSHEET_URL = "https://spreadsheets.google.com/feeds/list/1vzugrYLpgXmFODqysSx331Lhq8LpDQGJ4sQtwSMrtV4/1/public/values?alt=json";
    var CONST_FIELD_KEYS = {
        xq: "gsx$需求类别",
        qssj: "gsx$预计起始日期超过这个日期本条信息失效",
        qy: "gsx$区域",
        ybhzcs: "gsx$邮编或者城市",
        yqjg: "gsx$预期价格美元每月",
        grjs: "gsx$租房需求介绍和自我介绍",
        wxId: "gsx$微信号",
        xb: "gsx$性别",
        dhyx: "gsx$其他联系方式电话邮箱等",
        fbsj: "gsx$timestamp",
        ltst: "gsx$shorttermorlongterm",
        entryid: "gsx$entryid"
    };

    var CONST_FIELDS = [
        'xq', 'qssj', 'qy', 'ybhzcs', 'yqjg', 'grjs',
        'wxId', 'xb', 'dhyx', 'fbsj', 'ltst'
    ];

    var CONST_VALUE_ID_PREFIX = "#value_";
    var CONST_ITEM_TEMPLATE_ID = "#value_panel_template";
    var CONST_DOMAIN_NAME = "rent.zzn.im";
    /**
     * Class definition of Model
     * @constructor
     */
    var Model = function () {
        this.clearData(); // init with clear data
    };

    /**
     *
     * Creates a view as a clone of template
     * @constructor
     */
    var ItemView = function (guid) {
        this.guid_ = guid;
        this.templateClone_ = $(CONST_ITEM_TEMPLATE_ID).clone();
        this.guidLink_ = "http://" + CONST_DOMAIN_NAME + "/?" + $.param({"guid": guid});
    };

    var PanelView = function () {
        this.panelDom_ = $("#value_panel_parent");
    };

    /**
     * Class definition of Controller
     * @constructor
     */
    var Controller = function () {
        this.model_ = new Model();
        this.panelView_ = new PanelView();
    };

    // Set up cors proxy for jQuery
    $.ajaxPrefilter(function (options) {
        if (options.crossDomain && jQuery.support.cors) {
            var http = (window.location.protocol === 'http:' ? 'http:' : 'https:');
            options.url = http + '//cors-anywhere.herokuapp.com/' + options.url;
        }
    });

    Model.prototype.loadDataAsync = function (callback) {
        var that = this;
        $.get(CONST_SPREADSHEET_URL, function (data) {
            zHelper.log("loaded data from CORS proxy");
            that.cachedData_ = {}; //update data;
            data.feed.entry.forEach(function (row) {
                var guid = row[CONST_FIELD_KEYS.entryid]["$t"] || "N/A";
                var datum = {};
                CONST_FIELDS.forEach(function (fieldName) {
                    datum[fieldName] = row[CONST_FIELD_KEYS[fieldName]]["$t"] || "N/A";
                });
                that.cachedData_[guid] = datum;
                that.guidArray_.push(guid);
            });
            callback(data); // done
        });
    };

    Model.prototype.clearData = function () {
        this.cachedData_ = {};
        this.guidArray_ = [];
    };

    Model.prototype.getGuidArray = function () {
        return this.guidArray_;
    };

    Model.prototype.getFieldData = function (fieldName, guid) {
        return this.cachedData_[guid][fieldName];
    };

    /**
     * Add Guid Related Links
     * @param guid
     * @private
     */
    ItemView.prototype.decrorate_ = function () {
        var guid = this.guid_;
        var template = this.templateClone_;
        var guidLink = this.guidLink_;
        template.find("#qrcode").qrcode(guidLink);
        template.find("#row_link").attr("href", guidLink);
        template.find("#btn_share").attr("data-guid", guid);
        template.find("#btn_like").attr("data-guid", guid);
        template.show();
    };

    /**
     *
     * @param should boolean to indicate show(true) or hide(false)
     */
    ItemView.prototype.shouldShowContactInfo = function (should, model) {
        var template = this.templateClone_;
        if (should) {
            template.find("#btn_show_wxId").hide();
            template.find("#btn_show_dhyx").hide();
        } else {
            template.find("#value_wxId").hide();
            template.find("#value_dhyx").hide();

            template.find("#value_wxId").hide();
            template.find("#btn_show_wxId").click(function () {
                template.find("#btn_show_wxId").hide();
                template.find("#value_wxId").show();
            });
            template.find("#btn_show_dhyx").click(function () {
                template.find("#btn_show_dhyx").hide();
                template.find("#value_dhyx").show();
            });

        }
        //TODO(zzn): update this to somewhere proper
        template.find("#btn_share").click(function(){
            alert("clicked share, setting configureWeChat");
            that.configureWeChat_(model);
        });
    };

    ItemView.prototype.displayData = function (model) {
        var template = this.templateClone_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        if (xq == "出租") {
            template.addClass("panel-success");
        } else if (xq == "求租") {
            template.addClass("panel-primary");
        } else if (xq == "找室友") {
            template.addClass("panel-warning");
        }
        CONST_FIELDS.forEach(function (fieldName) {
            template.find(CONST_VALUE_ID_PREFIX + fieldName).text(model.getFieldData(fieldName, guid));
        });

        // Set up Id of template
        template.attr("id", "value_panel" + guid);
        this.decrorate_();
    };
    ItemView.prototype.getDom = function () {
        return this.templateClone_;
    };

    ItemView.prototype.configureWeChat_ = function(model){
        alert("clicked configure wechat!");
        var guidLink = this.guidLink_;
        var guid = this.guid_;
        var xq = model.getFieldData("xq", guid);
        var title, link, imgUrl, desc;
        alert("XXX 111");
        if (xq == "出租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-green-1024sq2.jpg";
        } else if (xq == "求租") {
            imgUrl = "http://" + CONST_DOMAIN_NAME + "/img/logo-v1-blue-1024sq2.jpg";
        } else if (xq == "找室友") {
            imgUrl = "http://" + CONST_DOMAIN_NAME +"/img/logo-v1-yellow-1024sq2.jpg";
        }
        alert("XXX 222");
        title = model.getFieldData("xq", guid) +
            model.getFieldData("qy", guid) + "，时间是"
        model.getFieldData("qssj", guid) + "左右开始，求分享！";
        alert("XXX 333");
        link = guidLink;
        desc = model.getFieldData("grjs", guid);
        alert("XXX 444");
        var wxData = {
            title: title,
            link: link,
            imgUrl: imgUrl,
            desc: desc,
            trigger: function (res) {
                alert('用户点击分享到微博');
            },
            complete: function (res) {
                alert(JSON.stringify(res));
            },
            success: function (res) {
                alert('已分享');
            },
            cancel: function (res) {
                alert('已取消');
            },
            fail: function (res) {
                alert(JSON.stringify(res));
            }
        };
        alert("XXX 555");
        alert(wxData);
        alert("XXX 666");
        wx.onMenuShareAppMessage(wxData);
        alert("XXX 777");

    };
    PanelView.prototype.addChild = function (item) {
        this.panelDom_.append(item);
    };

    PanelView.prototype.clearChildren = function () {
        this.panelDom_.empty();
    };

    Controller.prototype.initAsync = function (callback) {
        this.model_.loadDataAsync(callback);
    };

    Controller.prototype.showAllItems = function () {
        var that = this;
        this.panelView_.clearChildren();
        this.model_.getGuidArray().forEach(function (guid) {
            var item = new ItemView(guid);
            item.displayData(that.model_, guid);
            item.shouldShowContactInfo(false);
            that.panelView_.addChild(item.getDom());

        });
    };

    /**
     *
     * @param guid The GUID of a given listing post
     */
    Controller.prototype.showItemByGuid = function (guid) {
        this.panelView_.clearChildren();
        var that = this;
        var item = new ItemView(guid);
        item.displayData(this.model_, guid);
        item.shouldShowContactInfo(true);

        wx.ready(function(res){
            zHelper.log(res);
            wx.checkJsApi({
                jsApiList: [
                    "onMenuShareTimeline",
                    "onMenuShareAppMessage"
                ],
                success: function (res) {
                    alert("wxCheckJsApi, success");
                    zHelper.log(JSON.stringify(res));
                    // TODO(zzn):
                    // uncomment update it here
                    // item.configureWeChat_(that.model_);
                },
                fail: function (res) {
                    alert("wxCheckJsApi, faile");
                    zHelper.log(JSON.stringify(res));
                }
            });

        });

        zHelper.log("XXX after setting wechat");
        this.panelView_.addChild(item.getDom());
    };

    return {
        Controller: Controller
    };
})($, wx, zHelper);<|endoftext|>


<commit_before>
// @require core/colors/colors.js
// @require lib/transform/transform.js
// @require widgets/draggable/draggable.js

//TODO: Add flick support

(function ( $, _, Svelto, Factory, Colors ) {

  /* CONFIG */

  let config = {
    name: 'switch',
    plugin: true,
    selector: '.switch',
    options: {
      colors: {
        on: Colors.secondary,
        off: Colors.gray
      },
      datas: {
        colors: {
          on: 'color-on',
          off: 'color-off'
        }
      },
      classes: {
        checked: 'checked'
      },
      selectors: {
        input: 'input',
        bar: '.switch-bar',
        handler: '.switch-handler'
      },
      keystrokes: {
        'left': 'uncheck',
        'right': 'check',
        'spacebar': 'toggle'
      },
      callbacks: {
        change: _.noop,
        check: _.noop,
        uncheck: _.noop
      }
    }
  };

  /* SWITCH */

  class Switch extends Svelto.Widget {

    /* SPECIAL */

    _variables () {

      this.$switch = this.$element;
      this.$input = this.$switch.find ( this.options.selectors.input );
      this.$bar = this.$switch.find ( this.options.selectors.bar );
      this.$handler = this.$switch.find ( this.options.selectors.handler );

      this.isChecked = this.$input.prop ( 'checked' );

      this.switchWidth = this.$switch.width ();
      this.handlerWidth = this.$handler.width ();

    }

    _init () {

      /* OPTIONS */

      this.options.colors.on = this.$switch.data ( this.options.datas.colors.on ) || this.options.colors.on;
      this.options.colors.off = this.$switch.data ( this.options.datas.colors.off ) || this.options.colors.off;

      /* INITIAL SETTING */

      this._updateColors ();
      this._updatePosition ();

    }

    _events () {

      this.___change ();
      this.___keydown ();
      this.___drag ();

    }

    _destroy () {

      this.$handler.draggable ( 'destroy' );

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$input, 'change', this.__change );

    }

    __change () {

      this.toggle ( this.$input.prop ( 'checked' ) );

    }

    /* KEYDOWN */

    ___keydown () {

      this._onHover ( [$.$document, 'keydown', this.__keydown] );

    }

    /* DRAG */

    ___drag () {

      this.$handler.draggable ({
        draggable: this.isEnabled.bind ( this ),
        axis: 'x',
        proxy: {
          $element: this.$switch,
          noMotion: false
        },
        constrainer: {
          $element: this.$switch
        },
        callbacks: {
          end: this.__dragEnd.bind ( this )
        }
      });

    }

    __dragEnd ( event, data ) {

      if ( data.motion ) {

        let isChecked = ( data.dragXY.x + ( this.handlerWidth / 2 ) ) >= ( this.switchWidth / 2 );

        this.toggle ( isChecked, true );

      } else {

        this.toggle ();

      }

    }

    /* UPDATE */

    _updatePosition () {

      this.$handler.translateX ( this.isChecked ? this.switchWidth - this.handlerWidth : 0 );

    }

    _updateColors () {

      this.$bar.toggleClass ( this.options.colors.on, this.isChecked );
      this.$bar.toggleClass ( this.options.colors.off, !this.isChecked );

      this.$handler.toggleClass ( this.options.colors.on, this.isChecked );
      this.$handler.toggleClass ( this.options.colors.off, !this.isChecked );

    }

    _updateInput () {

      this.$input.prop ( 'checked', this.isChecked ).trigger ( 'change' );

    }

    _update () {

      this._updatePosition ();
      this._updateColors ();
      this._updateInput ();

    }

    /* API */

    get () {

      return this.isChecked;

    }

    toggle ( force, _reset ) {

      if ( !_.isBoolean ( force ) ) {

        force = !this.isChecked;

      }

      if ( force !== this.isChecked ) {

        this.isChecked = force;

        this.$switch.toggleClass ( this.options.classes.checked, this.isChecked );

        this._update ();

        this._trigger ( 'change' );

        this._trigger ( this.isChecked ? 'check' : 'uncheck' );

      } else if ( _reset ) {

        this._updatePosition ();

      }

    }

    check () {

      this.toggle ( true );

    }

    uncheck () {

      this.toggle ( false );

    }

  }

  /* FACTORY */

  Factory.make ( Switch, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Factory, Svelto.Colors ));
<commit_msg>Switch: improved revealing at a later time<commit_after>
// @require core/colors/colors.js
// @require lib/transform/transform.js
// @require widgets/draggable/draggable.js

//TODO: Add flick support

(function ( $, _, Svelto, Factory, Colors ) {

  /* CONFIG */

  let config = {
    name: 'switch',
    plugin: true,
    selector: '.switch',
    options: {
      colors: {
        on: Colors.secondary,
        off: Colors.gray
      },
      datas: {
        colors: {
          on: 'color-on',
          off: 'color-off'
        }
      },
      classes: {
        checked: 'checked'
      },
      selectors: {
        input: 'input',
        bar: '.switch-bar',
        handler: '.switch-handler'
      },
      keystrokes: {
        'left': 'uncheck',
        'right': 'check',
        'spacebar': 'toggle'
      },
      callbacks: {
        change: _.noop,
        check: _.noop,
        uncheck: _.noop
      }
    }
  };

  /* SWITCH */

  class Switch extends Svelto.Widget {

    /* SPECIAL */

    _variables () {

      this.$switch = this.$element;
      this.$input = this.$switch.find ( this.options.selectors.input );
      this.$bar = this.$switch.find ( this.options.selectors.bar );
      this.$handler = this.$switch.find ( this.options.selectors.handler );

      this.isChecked = this.$input.prop ( 'checked' );

      this.switchWidth = this.$switch.width () || 47; //FIXME: It shouldn't be set manually, but this widget might be hidden at init time
      this.handlerWidth = this.$handler.width () || 21; //FIXME: It shouldn't be set manually, but this widget might be hidden at init time

    }

    _init () {

      /* OPTIONS */

      this.options.colors.on = this.$switch.data ( this.options.datas.colors.on ) || this.options.colors.on;
      this.options.colors.off = this.$switch.data ( this.options.datas.colors.off ) || this.options.colors.off;

      /* INITIAL SETTING */

      this._updateColors ();
      this._updatePosition ();

    }

    _events () {

      this.___change ();
      this.___keydown ();
      this.___drag ();

    }

    _destroy () {

      this.$handler.draggable ( 'destroy' );

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$input, 'change', this.__change );

    }

    __change () {

      this.toggle ( this.$input.prop ( 'checked' ) );

    }

    /* KEYDOWN */

    ___keydown () {

      this._onHover ( [$.$document, 'keydown', this.__keydown] );

    }

    /* DRAG */

    ___drag () {

      this.$handler.draggable ({
        draggable: this.isEnabled.bind ( this ),
        axis: 'x',
        proxy: {
          $element: this.$switch,
          noMotion: false
        },
        constrainer: {
          $element: this.$switch
        },
        callbacks: {
          end: this.__dragEnd.bind ( this )
        }
      });

    }

    __dragEnd ( event, data ) {

      if ( data.motion ) {

        let isChecked = ( data.dragXY.x + ( this.handlerWidth / 2 ) ) >= ( this.switchWidth / 2 );

        this.toggle ( isChecked, true );

      } else {

        this.toggle ();

      }

    }

    /* UPDATE */

    _updatePosition () {

      this.$handler.translateX ( this.isChecked ? this.switchWidth - this.handlerWidth : 0 );

    }

    _updateColors () {

      this.$bar.toggleClass ( this.options.colors.on, this.isChecked );
      this.$bar.toggleClass ( this.options.colors.off, !this.isChecked );

      this.$handler.toggleClass ( this.options.colors.on, this.isChecked );
      this.$handler.toggleClass ( this.options.colors.off, !this.isChecked );

    }

    _updateInput () {

      this.$input.prop ( 'checked', this.isChecked ).trigger ( 'change' );

    }

    _update () {

      this._updatePosition ();
      this._updateColors ();
      this._updateInput ();

    }

    /* API */

    get () {

      return this.isChecked;

    }

    toggle ( force, _reset ) {

      if ( !_.isBoolean ( force ) ) {

        force = !this.isChecked;

      }

      if ( force !== this.isChecked ) {

        this.isChecked = force;

        this.$switch.toggleClass ( this.options.classes.checked, this.isChecked );

        this._update ();

        this._trigger ( 'change' );

        this._trigger ( this.isChecked ? 'check' : 'uncheck' );

      } else if ( _reset ) {

        this._updatePosition ();

      }

    }

    check () {

      this.toggle ( true );

    }

    uncheck () {

      this.toggle ( false );

    }

  }

  /* FACTORY */

  Factory.make ( Switch, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Factory, Svelto.Colors ));
<|endoftext|>


<commit_before>
// @require core/keyboard/keyboard.js
// @require widgets/icon/icon.js
// @require widgets/toast/toast.js

//FIXME: Auto focus on the partial input doesn't work good on mobile, the keyboard keeps opening and closing

(function ( $, _, Svelto, Factory, Colors, Icon, Sizes, Pointer, Keyboard ) {

  /* CONFIG */

  let config = {
    name: 'tagbox',
    plugin: true,
    selector: '.tagbox',
    templates: {
      tag: _.template ( `
        <div class="label tagbox-tag <%= o.color %> <%= o.size %> <%= o.css %>" data-tag-value="<%= o.value %>">
          <%= o.value %>
          <i class="icon <%= Svelto.Sizes.xsmall %> actionable tagbox-tag-remover"><%= Svelto.Icon ( 'close' ) %>

      ` )
    },
    options: {
      init: '', // Initial value
      tags: [],
      tag: {
        minLength: 3,
        color: Colors.gray,
        size: '',
        css: 'circular'
      },
      characters: {
        forbid: true, // Forbid or not
        forbidden: [ '<', '>', ';', '`' ],
        separator: ',', // It will also become kind of a forbidden character, used for insertion
        inserters: [Keyboard.keys.ENTER, Keyboard.keys.TAB] // They are keyCodes
      },
      addOnBlur: true, // Treat a blur event like a submit event
      editBackspace: true, // Enable editing the last tag when pressing backspace with an empty input
      sort: false, // The tags will be outputted in alphanumeric-sort order
      escape: false, // Escape potential XSS characters
      deburr: false, // Replace non basic-latin characters
      messages: {
        tooShort: '`$1` is shorter than $2 characters',
        duplicate: '`$1` is a duplicate',
        forbidden: 'The character you entered is forbidden'
      },
      datas: {
        value: 'tag-value'
      },
      selectors: {
        input: 'input.hidden',
        partial: 'input.tagbox-partial, .tagbox-partial input',
        tags: '.tagbox-tags',
        tag: '.tagbox-tag',
        tagLabel: 'span',
        tagRemover: '.tagbox-tag-remover'
      },
      callbacks: {
        change: _.noop,
        add: _.noop,
        remove: _.noop,
        empty: _.noop
      }
    }
  };

  /* TAGBOX */

  class Tagbox extends Svelto.Widget {

    /* SPECIAL */

    _variables () {

      this.$tagbox = this.$element;
      this.$tags = this.$tagbox.find ( this.options.selectors.tags );
      this.$input = this.$tagbox.find ( this.options.selectors.input );
      this.$partial = this.$tagbox.find ( this.options.selectors.partial );
      this.partial = this.$partial[0];

    }

    _init ( suppressTriggers ) {

      /* REMOVE PREVIOUS */

      this.$tagbox.find ( this.options.selectors.tag ).remove ();

      /* OPTIONS */

      this.options.init = this.$input.val () || this.options.init;

      /* POPULATING */

      this.add ( this.options.init, suppressTriggers );

    }

    _events () {

      this.___partial ();

      this.___tapOnEmpty ();
      this.___tapOnTagRemover ();

    }

    /* PRIVATE */

    _sanitizeTag ( value ) {

      value = value.trim ();

      if ( this.options.escape ) {

        value = _.escape ( value );

      }

      if ( this.options.deburr ) {

        value = _.deburr ( value );

      }

      return value;

    }

    _getTagHtml ( value ) {

      return this._template ( 'tag', _.extend ( { value: value }, this.options.tag ) );

    }

    _clearPartial () {

      this.$partial.val ( '' ).trigger ( 'change' );

    }

    /* UPDATE */

    _updateInput () {

      this.$input.val ( this.options.tags.join ( this.options.characters.separator ) ).trigger ( 'change' );

    }

    /* TAG */

    _add ( value ) {

      let valueTrimmed = value.trim ();

      value = this._sanitizeTag ( value );

      if ( valueTrimmed.length < this.options.tag.minLength ) {

        if ( valueTrimmed.length ) { // So it won't be triggered when the user presses enter and the $partial is empty

          $.toast ( _.format ( this.options.messages.tooShort, value, this.options.tag.minLength ) );

        }

      } else if ( this.options.tags.includes ( value ) ) {

        $.toast ( _.format ( this.options.messages.duplicate, value ) );

      } else {

        this.options.tags.push ( value );

        if ( this.options.sort ) {

          this.options.tags.sort ();

        }

        let tagHtml = this._getTagHtml ( value );

        if ( this.options.tags.length === 1 ) {

          this.$tags.prepend ( tagHtml );

        } else if ( !this.options.sort ) {

          this.$tagbox.find ( this.options.selectors.tag ).last ().after ( tagHtml );

        } else {

          let index = this.options.tags.indexOf ( value );

          if ( index === 0 ) {

            this.$tagbox.find ( this.options.selectors.tag ).first ().before ( tagHtml );

          } else {

            this.$tagbox.find ( this.options.selectors.tag ).eq ( index - 1 ).after ( tagHtml );

          }

        }

        return true;

      }

      return false;

    }

    _remove ( $tag, tag ) {

      $tag.remove ();

      _.pull ( this.options.tags, tag );

    }

    /* PARTIAL */

    ___partial () {

      this._on ( this.$partial, 'keypress keydown', this.__keypressKeydown ); // `keypress` is for printable characters, `keydown` for the others

      this._on ( this.$partial, 'paste', this.__paste );

      if ( this.options.addOnBlur ) this._on ( this.$partial, 'blur', this.___blur );

    }

    /* KEYPRESS / KEYDOWN */

    __keypressKeydown ( event ) {

      let value = this.$partial.val ();

      if ( this.options.characters.inserters.includes ( event.keyCode ) || event.keyCode === this.options.characters.separator.charCodeAt ( 0 ) ) {

        let added = this.add ( value );

        if ( added ) {

          this._clearPartial ();

        }

        event.preventDefault ();
        event.stopImmediatePropagation ();

      } else if ( event.keyCode === Keyboard.keys.BACKSPACE ) {

        if ( this.options.editBackspace && !value.length && this.options.tags.length ) {

          let $tag = this.$tagbox.find ( this.options.selectors.tag ).last (),
              edit = !Keyboard.keystroke.hasCtrlOrCmd ( event );

          this.remove ( $tag, edit );

          event.preventDefault ();
          event.stopImmediatePropagation ();

        }

      } else if ( this.options.characters.forbid && this.options.characters.forbidden.includes ( String.fromCharCode ( event.keyCode ) ) ) {

        $.toast ( this.options.messages.forbidden );

        event.preventDefault ();
        event.stopImmediatePropagation ();

      }

    }

    /* PASTE */

    __paste ( event ) {

      let originalEvent = event.originalEvent || event;

      this.add ( originalEvent.clipboardData.getData ( 'text' ) );

      event.preventDefault ();
      event.stopImmediatePropagation ();

    }

    /* BLUR */

    ___blur ( event ) {

      let value = this.$partial.val ();

      if ( !value ) return;

      let added = this.add ( value );

      if ( added ) this._clearPartial ();

    }

    /* TAP ON TAG REMOVER */

    ___tapOnTagRemover () {

      this._on ( Pointer.tap, this.options.selectors.tagRemover, this.__tapOnTagRemover );

    }

    __tapOnTagRemover ( event ) {

      event.stopImmediatePropagation ();

      let $tag = $(event.target).closest ( this.options.selectors.tag );

      this.remove ( $tag );

    }

    /* TAP ON EMPTY */

    ___tapOnEmpty () {

      this._on ( Pointer.tap, this.__tapOnEmpty );

    }

    __tapOnEmpty ( event ) {

      if ( !$.isFocused ( this.partial ) && !$(event.target).is ( this.options.selectors.partial + ',' + this.options.selectors.tagLabel ) ) {

        this.partial.focus ();

      }

    }

    /* API */

    get () {

      return _.cloneDeep ( this.options.tags );

    }

    add ( tag, suppressTriggers ) { // The tag can be a string containing a single tag, multiple tags separated by `this.options.characters.separator`, or it can be an array (nested or not) of those strings

      if ( _.isArray ( tag ) ) {

        tag = _.flatten ( tag ).join ( this.options.characters.separator );

      }

      let tags = tag.split ( this.options.characters.separator ),
          adds = tags.map ( tag => this._add ( tag ) );

      let added = !!adds.find ( _.identity );

      if ( added ) {

        this._updateInput ();

        if ( !suppressTriggers ) {

          this._trigger ( 'change' );

          let addedTags = tags.filter ( ( tag, index ) => adds[index] );

          this._trigger ( 'add', addedTags );

        }

      }

      return added;

    }

    remove ( tag, edit, suppressTriggers ) { // The tag can be a string containing a single tag, multiple tags separated by `this.options.characters.separator`, or it can be an array (nested or not) of those strings. In addition it can also be the jQuery object of that tag.

      let $tags = [],
          tags = [];

      if ( tag instanceof $ ) {

        $tags = [tag];
        tags = [tag.data ( this.options.datas.value )];

      } else {

        if ( _.isArray ( tag ) ) {

          tag = _.flatten ( tag ).join ( this.options.characters.separator );

        }

        tag = tag.split ( this.options.characters.separator );

        for ( let i = 0, l = tag.length; i < l; i++ ) {

          let value = this._sanitizeTag ( tag[i] ),
              $tag = this.$tagbox.find ( this.options.selectors.tag + '[data-' + this.options.datas.value + '="' + value.replace ( /"/g, '\\"' ) + '"]' );

          if ( $tag.length === 1 ) {

            $tags.push ( $tag );
            tags.push ( value );

          }

        }

      }

      if ( tags.length ) {

        for ( let i = 0, l = tags.length; i < l; i++ ) {

          this._remove ( $tags[i], tags[i] );

        }

        this._updateInput ();

        if ( tags.length === 1 && edit === true ) {

          this.$partial.val ( tags[0] ).trigger ( 'change' );

        }

        if ( !suppressTriggers ) {

          this._trigger ( 'change' );

          this._trigger ( 'remove', tags );

          if ( !this.options.tags.length ) {

            this._trigger ( 'empty' );

          }

        }

      }

    }

    clear ( suppressTriggers ) {

      if ( this.options.tags.length ) {

        let previous = this.options.tags;

        this.options.tags = [];

        this.$tagbox.find ( this.options.selectors.tag ).remove ();

        this._clearPartial ();

        this._updateInput ();

        if ( !suppressTriggers ) {

          this._trigger ( 'change' );

          this._trigger ( 'remove', previous );

          this._trigger ( 'empty' );

        }

      }

    }

    reset () {

      let previous = this.options.tags;

      this.clear ( true );

      this._init ( true );

      if ( !_.isEqualJSON ( previous, this.options.tags ) ) {

        this._trigger ( 'change' );

        let added = _.difference ( this.options.tags, previous );

        if ( added.length ) {

          this._trigger ( 'add', added );

        }

        let removed = _.difference ( previous, this.options.tags );

        if ( removed.length ) {

          this._trigger ( 'remove', removed );

        }

        if ( !this.options.tags.length ) {

          this._trigger ( 'empty' );

        }

      }

    }

  }

  /* FACTORY */

  Factory.make ( Tagbox, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Factory, Svelto.Colors, Svelto.Icon, Svelto.Sizes, Svelto.Pointer, Svelto.Keyboard ));
<commit_msg>Tagbox: improved support for tagboxes without rendered tags<commit_after>
// @require core/keyboard/keyboard.js
// @require widgets/icon/icon.js
// @require widgets/toast/toast.js

//FIXME: Auto focus on the partial input doesn't work good on mobile, the keyboard keeps opening and closing

(function ( $, _, Svelto, Factory, Colors, Icon, Sizes, Pointer, Keyboard ) {

  /* CONFIG */

  let config = {
    name: 'tagbox',
    plugin: true,
    selector: '.tagbox',
    templates: {
      tag: _.template ( `
        <div class="label tagbox-tag <%= o.color %> <%= o.size %> <%= o.css %>" data-tag-value="<%= o.value %>">
          <%= o.value %>
          <i class="icon <%= Svelto.Sizes.xsmall %> actionable tagbox-tag-remover"><%= Svelto.Icon ( 'close' ) %>

      ` )
    },
    options: {
      init: '', // Initial value
      tags: [],
      tag: {
        minLength: 3,
        color: Colors.gray,
        size: '',
        css: 'circular'
      },
      characters: {
        forbid: true, // Forbid or not
        forbidden: [ '<', '>', ';', '`' ],
        separator: ',', // It will also become kind of a forbidden character, used for insertion
        inserters: [Keyboard.keys.ENTER, Keyboard.keys.TAB] // They are keyCodes
      },
      addOnBlur: true, // Treat a blur event like a submit event
      editBackspace: true, // Enable editing the last tag when pressing backspace with an empty input
      sort: false, // The tags will be outputted in alphanumeric-sort order
      escape: false, // Escape potential XSS characters
      deburr: false, // Replace non basic-latin characters
      messages: {
        tooShort: '`$1` is shorter than $2 characters',
        duplicate: '`$1` is a duplicate',
        forbidden: 'The character you entered is forbidden'
      },
      datas: {
        value: 'tag-value'
      },
      selectors: {
        input: 'input.hidden',
        partial: 'input.tagbox-partial, .tagbox-partial input',
        tags: '.tagbox-tags',
        tag: '.tagbox-tag',
        tagLabel: 'span',
        tagRemover: '.tagbox-tag-remover'
      },
      callbacks: {
        change: _.noop,
        add: _.noop,
        remove: _.noop,
        empty: _.noop
      }
    }
  };

  /* TAGBOX */

  class Tagbox extends Svelto.Widget {

    /* SPECIAL */

    _variables () {

      this.$tagbox = this.$element;
      this.$tags = this.$tagbox.find ( this.options.selectors.tags );
      this.$input = this.$tagbox.find ( this.options.selectors.input );
      this.$partial = this.$tagbox.find ( this.options.selectors.partial );
      this.partial = this.$partial[0];

    }

    _init ( suppressTriggers ) {

      /* REMOVE PREVIOUS */

      this.$tagbox.find ( this.options.selectors.tag ).remove ();

      /* OPTIONS */

      this.options.init = this.$input.val () || this.options.init;

      /* POPULATING */

      this.add ( this.options.init, suppressTriggers );

    }

    _events () {

      this.___partial ();

      this.___tapOnEmpty ();
      this.___tapOnTagRemover ();

    }

    /* PRIVATE */

    _sanitizeTag ( value ) {

      value = value.trim ();

      if ( this.options.escape ) {

        value = _.escape ( value );

      }

      if ( this.options.deburr ) {

        value = _.deburr ( value );

      }

      return value;

    }

    _getTagHtml ( value ) {

      return this._template ( 'tag', _.extend ( { value: value }, this.options.tag ) );

    }

    _clearPartial () {

      this.$partial.val ( '' ).trigger ( 'change' );

    }

    /* UPDATE */

    _updateInput () {

      this.$input.val ( this.options.tags.join ( this.options.characters.separator ) ).trigger ( 'change' );

    }

    /* TAG */

    _add ( value ) {

      let valueTrimmed = value.trim ();

      value = this._sanitizeTag ( value );

      if ( valueTrimmed.length < this.options.tag.minLength ) {

        if ( valueTrimmed.length ) { // So it won't be triggered when the user presses enter and the $partial is empty

          $.toast ( _.format ( this.options.messages.tooShort, value, this.options.tag.minLength ) );

        }

      } else if ( this.options.tags.includes ( value ) ) {

        $.toast ( _.format ( this.options.messages.duplicate, value ) );

      } else {

        this.options.tags.push ( value );

        if ( this.options.sort ) {

          this.options.tags.sort ();

        }

        let tagHtml = this._getTagHtml ( value );

        if ( this.options.tags.length === 1 ) {

          this.$tags.prepend ( tagHtml );

        } else if ( !this.options.sort ) {

          this.$tagbox.find ( this.options.selectors.tag ).last ().after ( tagHtml );

        } else {

          let index = this.options.tags.indexOf ( value );

          if ( index === 0 ) {

            this.$tagbox.find ( this.options.selectors.tag ).first ().before ( tagHtml );

          } else {

            this.$tagbox.find ( this.options.selectors.tag ).eq ( index - 1 ).after ( tagHtml );

          }

        }

        return true;

      }

      return false;

    }

    _remove ( $tag, tag ) {

      $tag.remove ();

      _.pull ( this.options.tags, tag );

    }

    /* PARTIAL */

    ___partial () {

      this._on ( this.$partial, 'keypress keydown', this.__keypressKeydown ); // `keypress` is for printable characters, `keydown` for the others

      this._on ( this.$partial, 'paste', this.__paste );

      if ( this.options.addOnBlur ) this._on ( this.$partial, 'blur', this.___blur );

    }

    /* KEYPRESS / KEYDOWN */

    __keypressKeydown ( event ) {

      let value = this.$partial.val ();

      if ( this.options.characters.inserters.includes ( event.keyCode ) || event.keyCode === this.options.characters.separator.charCodeAt ( 0 ) ) {

        let added = this.add ( value );

        if ( added ) {

          this._clearPartial ();

        }

        event.preventDefault ();
        event.stopImmediatePropagation ();

      } else if ( event.keyCode === Keyboard.keys.BACKSPACE ) {

        if ( this.options.editBackspace && !value.length && this.options.tags.length ) {

          let $tag = this.$tagbox.find ( this.options.selectors.tag ).last ();

          if ( $tag.length ) {

            let edit = !Keyboard.keystroke.hasCtrlOrCmd ( event );

            this.remove ( $tag, edit );

            event.preventDefault ();
            event.stopImmediatePropagation ();

          }

        }

      } else if ( this.options.characters.forbid && this.options.characters.forbidden.includes ( String.fromCharCode ( event.keyCode ) ) ) {

        $.toast ( this.options.messages.forbidden );

        event.preventDefault ();
        event.stopImmediatePropagation ();

      }

    }

    /* PASTE */

    __paste ( event ) {

      let originalEvent = event.originalEvent || event;

      this.add ( originalEvent.clipboardData.getData ( 'text' ) );

      event.preventDefault ();
      event.stopImmediatePropagation ();

    }

    /* BLUR */

    ___blur ( event ) {

      let value = this.$partial.val ();

      if ( !value ) return;

      let added = this.add ( value );

      if ( added ) this._clearPartial ();

    }

    /* TAP ON TAG REMOVER */

    ___tapOnTagRemover () {

      this._on ( Pointer.tap, this.options.selectors.tagRemover, this.__tapOnTagRemover );

    }

    __tapOnTagRemover ( event ) {

      event.stopImmediatePropagation ();

      let $tag = $(event.target).closest ( this.options.selectors.tag );

      this.remove ( $tag );

    }

    /* TAP ON EMPTY */

    ___tapOnEmpty () {

      this._on ( Pointer.tap, this.__tapOnEmpty );

    }

    __tapOnEmpty ( event ) {

      if ( !$.isFocused ( this.partial ) && !$(event.target).is ( this.options.selectors.partial + ',' + this.options.selectors.tagLabel ) ) {

        this.partial.focus ();

      }

    }

    /* API */

    get () {

      return _.cloneDeep ( this.options.tags );

    }

    add ( tag, suppressTriggers ) { // The tag can be a string containing a single tag, multiple tags separated by `this.options.characters.separator`, or it can be an array (nested or not) of those strings

      if ( _.isArray ( tag ) ) {

        tag = _.flatten ( tag ).join ( this.options.characters.separator );

      }

      let tags = tag.split ( this.options.characters.separator ),
          adds = tags.map ( tag => this._add ( tag ) );

      let added = !!adds.find ( _.identity );

      if ( added ) {

        this._updateInput ();

        if ( !suppressTriggers ) {

          this._trigger ( 'change' );

          let addedTags = tags.filter ( ( tag, index ) => adds[index] );

          this._trigger ( 'add', addedTags );

        }

      }

      return added;

    }

    remove ( tag, edit, suppressTriggers ) { // The tag can be a string containing a single tag, multiple tags separated by `this.options.characters.separator`, or it can be an array (nested or not) of those strings. In addition it can also be the jQuery object of that tag.

      let $tags = [],
          tags = [];

      if ( tag instanceof $ ) {

        $tags = [tag];
        tags = [tag.data ( this.options.datas.value )];

      } else {

        if ( _.isArray ( tag ) ) {

          tag = _.flatten ( tag ).join ( this.options.characters.separator );

        }

        tag = tag.split ( this.options.characters.separator );

        for ( let i = 0, l = tag.length; i < l; i++ ) {

          let value = this._sanitizeTag ( tag[i] ),
              $tag = this.$tagbox.find ( this.options.selectors.tag + '[data-' + this.options.datas.value + '="' + value.replace ( /"/g, '\\"' ) + '"]' );

          if ( $tag.length === 1 ) {

            $tags.push ( $tag );
            tags.push ( value );

          }

        }

      }

      if ( tags.length ) {

        for ( let i = 0, l = tags.length; i < l; i++ ) {

          this._remove ( $tags[i], tags[i] );

        }

        this._updateInput ();

        if ( tags.length === 1 && edit === true ) {

          this.$partial.val ( tags[0] ).trigger ( 'change' );

        }

        if ( !suppressTriggers ) {

          this._trigger ( 'change' );

          this._trigger ( 'remove', tags );

          if ( !this.options.tags.length ) {

            this._trigger ( 'empty' );

          }

        }

      }

    }

    clear ( suppressTriggers ) {

      if ( this.options.tags.length ) {

        let previous = this.options.tags;

        this.options.tags = [];

        this.$tagbox.find ( this.options.selectors.tag ).remove ();

        this._clearPartial ();

        this._updateInput ();

        if ( !suppressTriggers ) {

          this._trigger ( 'change' );

          this._trigger ( 'remove', previous );

          this._trigger ( 'empty' );

        }

      }

    }

    reset () {

      let previous = this.options.tags;

      this.clear ( true );

      this._init ( true );

      if ( !_.isEqualJSON ( previous, this.options.tags ) ) {

        this._trigger ( 'change' );

        let added = _.difference ( this.options.tags, previous );

        if ( added.length ) {

          this._trigger ( 'add', added );

        }

        let removed = _.difference ( previous, this.options.tags );

        if ( removed.length ) {

          this._trigger ( 'remove', removed );

        }

        if ( !this.options.tags.length ) {

          this._trigger ( 'empty' );

        }

      }

    }

  }

  /* FACTORY */

  Factory.make ( Tagbox, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Factory, Svelto.Colors, Svelto.Icon, Svelto.Sizes, Svelto.Pointer, Svelto.Keyboard ));
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import cx from 'classnames';

import defaultTemplates from './defaultTemplates.js';
import RefinementList from '../../components/RefinementList/RefinementList.js';
import connectToggle from '../../connectors/toggle/connectToggle.js';

import {
  bemHelper,
  getContainerNode,
  prepareTemplateProps,
} from '../../lib/utils.js';

const bem = bemHelper('ais-toggle');

const renderer = ({
  containerNode,
  cssClasses,
  collapsible,
  autoHideContainer,
  renderState,
  templates,
  transformData,
}) => ({
  value,
  createURL,
  refine,
  instantSearchInstance,
}, isFirstRendering) => {
  if (isFirstRendering) {
    renderState.templateProps = prepareTemplateProps({
      transformData,
      defaultTemplates,
      templatesConfig: instantSearchInstance.templatesConfig,
      templates,
    });
    return;
  }

  const shouldAutoHideContainer = autoHideContainer && (value.count === 0 || value.count === null);

  ReactDOM.render(
    <RefinementList
      collapsible={collapsible}
      createURL={createURL}
      cssClasses={cssClasses}
      facetValues={[value]}
      shouldAutoHideContainer={shouldAutoHideContainer}
      templateProps={renderState.templateProps}
      toggleRefinement={(name, isRefined) => refine({isRefined})}
    />,
    containerNode
  );
};

const usage = `Usage:
toggle({
  container,
  attributeName,
  label,
  [ values={on: true, off: undefined} ],
  [ cssClasses.{root,header,body,footer,list,item,active,label,checkbox,count} ],
  [ templates.{header,item,footer} ],
  [ transformData.{item} ],
  [ autoHideContainer=true ],
  [ collapsible=false ]
})`;

/**
 * @typedef {Object} ToggleWidgetCSSClasses
 * @property  {string|string[]} [root] CSS class to add to the root element.
 * @property  {string|string[]} [header] CSS class to add to the header element.
 * @property  {string|string[]} [body] CSS class to add to the body element.
 * @property  {string|string[]} [footer] CSS class to add to the footer element.
 * @property  {string|string[]} [list] CSS class to add to the list element.
 * @property  {string|string[]} [item] CSS class to add to each item element.
 * @property  {string|string[]} [active] CSS class to add to each active element.
 * @property  {string|string[]} [label] CSS class to add to each
 * label element (when using the default template).
 * @property  {string|string[]} [checkbox] CSS class to add to each
 * checkbox element (when using the default template).
 * @property  {string|string[]} [count] CSS class to add to each count.
 */

/**
 * @typedef {Object} ToggleWidgetTransforms
 * @property  {function(Object):Object} item Function to change the object passed to the `item`. template
 */

/**
 * @typedef {Object} ToggleWidgetTemplates
 * @property  {string|function} header Header template.
 * @property  {string|function} item Item template, provided with `name`, `count`, `isRefined`, `url` data properties.
 * count is always the number of hits that would be shown if you toggle the widget. We also provide
 * `onFacetValue` and `offFacetValue` objects with according counts.
 * @property  {string|function} footer Footer template.
 */

/**
 * @typedef {Object} ToggleWidgetValues
 * @property  {string|number|boolean} on Value to filter on when checked.
 * @property  {string|number|boolean} off Value to filter on when unchecked.
 * element (when using the default template). By default when switching to `off`, no refinement will be asked. So you
 * will get both `true` and `false` results. If you set the off value to `false` then you will get only objects
 * having `false` has a value for the selected attribute.
 */

/**
 * @typedef {Object} ToggleWidgetCollapsibleOption
 * @property {boolean} collapsed If set to true, the widget will be collapsed at first rendering.
 */

/**
 * @typedef {Object} ToggleWidgetOptions
 * @property {string|HTMLElement} container Place where to insert the widget in your webpage.
 * @property {string} attributeName Name of the attribute for faceting (eg. "free_shipping").
 * @property {string} label Human-readable name of the filter (eg. "Free Shipping").
 * @property {ToggleWidgetValues} [values={on: true, off: undefined}] Values that the widget can set.
 * @property {ToggleWidgetTemplates} [templates] Templates to use for the widget.
 * @property {ToggleWidgetTransforms} [transformData] Object that contains the functions to be applied on the data * before being used for templating. Valid keys are `body` for the body template.
 * @property {boolean} [autoHideContainer=true] Make the widget hides itself when there is no results matching.
 * @property {ToggleWidgetCSSClasses} [cssClasses] CSS classes to add.
 * @property {boolean|ToggleWidgetCollapsibleOption} collapsible If set to true, the widget can be collapsed. This parameter can also be
 * an object, with the property collapsed, if you want the toggle to be collapsed initially.
 */

/**
 * The toggle widget lets the user either:
 *  - switch between two values for a single facetted attribute (free_shipping / not_free_shipping)
 *  - toggle a faceted value on and off (only 'canon' for brands)
 *
 * This widget is particularly useful if you have a boolean value in the records.
 *
 * The attribute has to in the list of attributes for faceting in the dashboard.
 * @type {WidgetFactory}
 * @param {ToggleWidgetOptions} $0 Options for the Toggle widget.
 * @return {Widget} A new instance of the Toggle widget
 * @example
 * search.addWidget(
 *   instantsearch.widgets.toggle({
 *     container: '#free-shipping',
 *     attributeName: 'free_shipping',
 *     label: 'Free Shipping',
 *     values: {
 *       on: true,
 *       off: false
 *     },
 *     templates: {
 *       header: 'Shipping'
 *     }
 *   })
 * );
 */
export default function toggle({
  container,
  attributeName,
  label,
  cssClasses: userCssClasses = {},
  templates = defaultTemplates,
  transformData,
  autoHideContainer = true,
  collapsible = false,
  values: userValues = {on: true, off: undefined},
} = {}) {
  if (!container) {
    throw new Error(usage);
  }

  const containerNode = getContainerNode(container);

  const cssClasses = {
    root: cx(bem(null), userCssClasses.root),
    header: cx(bem('header'), userCssClasses.header),
    body: cx(bem('body'), userCssClasses.body),
    footer: cx(bem('footer'), userCssClasses.footer),
    list: cx(bem('list'), userCssClasses.list),
    item: cx(bem('item'), userCssClasses.item),
    active: cx(bem('item', 'active'), userCssClasses.active),
    label: cx(bem('label'), userCssClasses.label),
    checkbox: cx(bem('checkbox'), userCssClasses.checkbox),
    count: cx(bem('count'), userCssClasses.count),
  };

  const specializedRenderer = renderer({
    containerNode,
    cssClasses,
    collapsible,
    autoHideContainer,
    renderState: {},
    templates,
    transformData,
  });

  try {
    const makeWidget = connectToggle(specializedRenderer);
    return makeWidget({attributeName, label, values: userValues});
  } catch (e) {
    throw new Error(usage);
  }
}
<commit_msg>docs(toggle): fix spelling (#2225)<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import cx from 'classnames';

import defaultTemplates from './defaultTemplates.js';
import RefinementList from '../../components/RefinementList/RefinementList.js';
import connectToggle from '../../connectors/toggle/connectToggle.js';

import {
  bemHelper,
  getContainerNode,
  prepareTemplateProps,
} from '../../lib/utils.js';

const bem = bemHelper('ais-toggle');

const renderer = ({
  containerNode,
  cssClasses,
  collapsible,
  autoHideContainer,
  renderState,
  templates,
  transformData,
}) => ({
  value,
  createURL,
  refine,
  instantSearchInstance,
}, isFirstRendering) => {
  if (isFirstRendering) {
    renderState.templateProps = prepareTemplateProps({
      transformData,
      defaultTemplates,
      templatesConfig: instantSearchInstance.templatesConfig,
      templates,
    });
    return;
  }

  const shouldAutoHideContainer = autoHideContainer && (value.count === 0 || value.count === null);

  ReactDOM.render(
    <RefinementList
      collapsible={collapsible}
      createURL={createURL}
      cssClasses={cssClasses}
      facetValues={[value]}
      shouldAutoHideContainer={shouldAutoHideContainer}
      templateProps={renderState.templateProps}
      toggleRefinement={(name, isRefined) => refine({isRefined})}
    />,
    containerNode
  );
};

const usage = `Usage:
toggle({
  container,
  attributeName,
  label,
  [ values={on: true, off: undefined} ],
  [ cssClasses.{root,header,body,footer,list,item,active,label,checkbox,count} ],
  [ templates.{header,item,footer} ],
  [ transformData.{item} ],
  [ autoHideContainer=true ],
  [ collapsible=false ]
})`;

/**
 * @typedef {Object} ToggleWidgetCSSClasses
 * @property  {string|string[]} [root] CSS class to add to the root element.
 * @property  {string|string[]} [header] CSS class to add to the header element.
 * @property  {string|string[]} [body] CSS class to add to the body element.
 * @property  {string|string[]} [footer] CSS class to add to the footer element.
 * @property  {string|string[]} [list] CSS class to add to the list element.
 * @property  {string|string[]} [item] CSS class to add to each item element.
 * @property  {string|string[]} [active] CSS class to add to each active element.
 * @property  {string|string[]} [label] CSS class to add to each
 * label element (when using the default template).
 * @property  {string|string[]} [checkbox] CSS class to add to each
 * checkbox element (when using the default template).
 * @property  {string|string[]} [count] CSS class to add to each count.
 */

/**
 * @typedef {Object} ToggleWidgetTransforms
 * @property  {function(Object):Object} item Function to change the object passed to the `item`. template
 */

/**
 * @typedef {Object} ToggleWidgetTemplates
 * @property  {string|function} header Header template.
 * @property  {string|function} item Item template, provided with `name`, `count`, `isRefined`, `url` data properties.
 * count is always the number of hits that would be shown if you toggle the widget. We also provide
 * `onFacetValue` and `offFacetValue` objects with according counts.
 * @property  {string|function} footer Footer template.
 */

/**
 * @typedef {Object} ToggleWidgetValues
 * @property  {string|number|boolean} on Value to filter on when checked.
 * @property  {string|number|boolean} off Value to filter on when unchecked.
 * element (when using the default template). By default when switching to `off`, no refinement will be asked. So you
 * will get both `true` and `false` results. If you set the off value to `false` then you will get only objects
 * having `false` has a value for the selected attribute.
 */

/**
 * @typedef {Object} ToggleWidgetCollapsibleOption
 * @property {boolean} collapsed If set to true, the widget will be collapsed at first rendering.
 */

/**
 * @typedef {Object} ToggleWidgetOptions
 * @property {string|HTMLElement} container Place where to insert the widget in your webpage.
 * @property {string} attributeName Name of the attribute for faceting (eg. "free_shipping").
 * @property {string} label Human-readable name of the filter (eg. "Free Shipping").
 * @property {ToggleWidgetValues} [values={on: true, off: undefined}] Values that the widget can set.
 * @property {ToggleWidgetTemplates} [templates] Templates to use for the widget.
 * @property {ToggleWidgetTransforms} [transformData] Object that contains the functions to be applied on the data * before being used for templating. Valid keys are `body` for the body template.
 * @property {boolean} [autoHideContainer=true] Make the widget hides itself when there is no results matching.
 * @property {ToggleWidgetCSSClasses} [cssClasses] CSS classes to add.
 * @property {boolean|ToggleWidgetCollapsibleOption} collapsible If set to true, the widget can be collapsed. This parameter can also be
 * an object, with the property collapsed, if you want the toggle to be collapsed initially.
 */

/**
 * The toggle widget lets the user either:
 *  - switch between two values for a single facetted attribute (free_shipping / not_free_shipping)
 *  - toggle a faceted value on and off (only 'canon' for brands)
 *
 * This widget is particularly useful if you have a boolean value in the records.
 *
 * The attribute has to be in the list of attributes for faceting in the dashboard.
 * @type {WidgetFactory}
 * @param {ToggleWidgetOptions} $0 Options for the Toggle widget.
 * @return {Widget} A new instance of the Toggle widget
 * @example
 * search.addWidget(
 *   instantsearch.widgets.toggle({
 *     container: '#free-shipping',
 *     attributeName: 'free_shipping',
 *     label: 'Free Shipping',
 *     values: {
 *       on: true,
 *       off: false
 *     },
 *     templates: {
 *       header: 'Shipping'
 *     }
 *   })
 * );
 */
export default function toggle({
  container,
  attributeName,
  label,
  cssClasses: userCssClasses = {},
  templates = defaultTemplates,
  transformData,
  autoHideContainer = true,
  collapsible = false,
  values: userValues = {on: true, off: undefined},
} = {}) {
  if (!container) {
    throw new Error(usage);
  }

  const containerNode = getContainerNode(container);

  const cssClasses = {
    root: cx(bem(null), userCssClasses.root),
    header: cx(bem('header'), userCssClasses.header),
    body: cx(bem('body'), userCssClasses.body),
    footer: cx(bem('footer'), userCssClasses.footer),
    list: cx(bem('list'), userCssClasses.list),
    item: cx(bem('item'), userCssClasses.item),
    active: cx(bem('item', 'active'), userCssClasses.active),
    label: cx(bem('label'), userCssClasses.label),
    checkbox: cx(bem('checkbox'), userCssClasses.checkbox),
    count: cx(bem('count'), userCssClasses.count),
  };

  const specializedRenderer = renderer({
    containerNode,
    cssClasses,
    collapsible,
    autoHideContainer,
    renderState: {},
    templates,
    transformData,
  });

  try {
    const makeWidget = connectToggle(specializedRenderer);
    return makeWidget({attributeName, label, values: userValues});
  } catch (e) {
    throw new Error(usage);
  }
}
<|endoftext|>