<commit_before>/**
 * Created by leon on 15/11/24.
 */

angular.module("ui.neptune.directive.grid",
    ['ui.grid', "ui.grid.pagination", 'ui.grid.resizeColumns',
        'ui.grid.moveColumns', 'ui.grid.autoResize', 'ngRoute',
        'ng-context-menu', 'ui.bootstrap'])
    .provider("nptGridStore", function () {

        this.$get = function () {

            function NptGridStore(name, setting) {
                if (!name) {
                    throw new Error("npt grid store must set name.");
                }
                this._name = name;
                if (setting) {
                    this.setting(setting);
                }
            }

            NptGridStore.prototype.setting = function (setting) {
                this._gridOptions = {
                    paginationPageSizes: [10, 20, 30],
                    paginationPageSize: 10,
                    showGridFooter: true,
                    showColumnFooter: true,
                    //enableSorting: false,
                    enableRowSelection: true,
                    enableSelectAll: false,
                    multiSelect: false,
                    enableFullRowSelection: true,
                    selectionRowHeaderWidth: 35,
                    rowHeight: 35,
                    rowTemplate: "/template/grid/npt-grid-row.html"
                };
                this._gridOptions = angular.extend(setting.gridOptions, this._gridOptions);
                this._action = setting.action;
            };

            NptGridStore.prototype.gridOptions = function () {
                return this._gridOptions;
            };

            NptGridStore.prototype.action = function () {
                return this._action;
            };

            function nptGridStoreFactory(name, setting) {
                return new NptGridStore(name, setting);
            }

            return nptGridStoreFactory;
        };

    })
    .controller("GridController", function ($scope, i18nService, $q, $injector, $uibModal, nptFormStore) {
        var vm = this;

        //设置中文
        i18nService.setCurrentLang('zh-cn');

        vm.forms = {
            init: function () {
            },
            open: function (name, data) {
                this.originData = data;

                var result = $uibModal.open({
                    animation: true,
                    templateUrl: '/template/grid/grid-edit.html',
                    controller: 'editGridController',
                    controllerAs: 'vm',
                    resolve: {
                        formData: function ($q) {
                            var deferd = $q.defer();
                            nptFormStore.form(name, function (config) {
                                deferd.resolve({
                                    fields: angular.copy(config.fields), //拷贝新版本,防止修改原始配置
                                    model: angular.copy(data), //拷贝新版本,防止修改原始配置
                                    options: angular.copy(config.options) //拷贝新版本,防止修改原始配置
                                });
                            });
                            return deferd.promise;
                        }
                    }
                }).result;

                return result;
            },
            close: function () {
                $uibModal.close();
            }
        };

        function NptGridApi(nptGridOptions) {
            var self = this;
            this._options = nptGridOptions;
            this._config = {};
            if (nptGridOptions.store) {
                this._config.gridOptions = nptGridOptions.store.gridOptions() || {};
                this._config.action = nptGridOptions.store.action();
                //设置注册Api回调,如果用户在配置中设置会被替换
                this._config.gridOptions.onRegisterApi = function (uiGridApi) {
                    self.uiGridApi = uiGridApi;

                    //如果存在注册Api回调则执行
                    if (self._options.onRegisterApi) {
                        self._options.onRegisterApi(self);
                    }
                };

                //添加操作区域
                this._config.gridOptions.columnDefs.push({
                    field: 'actionScope',
                    displayName: "操作",
                    width: 100,
                    cellTemplate: "/template/grid/npt-grid-row-cell.html"
                });

                //添加序号区域,
                this._config.gridOptions.columnDefs.unshift({
                    field: '$index',
                    displayName: "序号",
                    enableSorting: true,
                    width: 60
                });

                // 增加菜单动作
                this.action = {};
                if (this._config.action) {
                    angular.forEach(this._config.action, function (action, key) {
                        self.action[key] = function () {
                            return self.triggerAction(action);
                        };

                        self.action[key].addListener = function (listener) {
                            action.listens = action.listens || [];
                            if (listener) {
                                action.listens.push(listener);
                            }
                        };
                    });
                }

                //设置数据
                this._config.gridOptions.data = "model";
            }

            // 定义框架支持的菜单操作
            this._handler = {
                fireListener:function(action, params) {
                    var deferd = $q.defer();
                    var promise = deferd.promise;
                    deferd.resolve(params);
                    var promisesArr = [promise];
                    //配置中的listen
                    angular.forEach(action.listens, function (listen) {
                        promisesArr.push($q.when($injector.invoke(listen, this, {
                            "params": params
                        })));
                    });
                    return $q.all(promisesArr);
                },
                add: function (action, item, index) {
                    var _handlerSelf = this;
                    var result = vm.forms.open(action.target, {});
                    if (result) {
                        result = result.then(function (data) {
                            var params = {
                                action: action,
                                index: index,
                                data: data
                            };

                            _handlerSelf.fireListener(action,params).then(function () {
                                //执行成功,将数据添加到表格
                                $scope.model.unshift(params.data);
                                console.info("添加执行成功.");
                            }, function (error) {
                                console.info("添加执行失败!" + JSON.stringify(error));
                            });

                        }, function (error) {
                            console.info("添加操作取消");
                        });
                        return result;
                    }
                },
                del: function (action, item, index) {
                    var params = {
                        action: action,
                        item: item,
                        index: index
                    };
                    var result = this.fireListener(action,params).then(function () {
                        $scope.model.splice(params.index, 1);
                    }, function (error) {
                        console.info("删除失败!" + error);
                    });

                    return result;
                },
                edit: function (action, item, index) {
                    var _handlerSelf = this;
                    var result = vm.forms.open(action.target, item);
                    if (result) {
                        result.then(function (data) {
                            //重新组织参数
                            var params = {
                                action: action,
                                index: index,
                                data: data,
                                item: angular.copy(item) //防止修改
                            };

                            _handlerSelf.fireListener(action,params).then(function (data) {
                                console.info("编辑操作成功!");
                                //将新数据更新到表格
                                angular.extend(item, params.data);
                            }, function (error) {
                                console.info("编辑操作失败!");
                            });

                        }, function (error) {
                            console.info("编辑操作取消");
                        });
                    }

                    return result;
                },
                none: function (action, item, index) {
                    var params = {
                        action: action,
                        item: angular.copy(item),
                        index: index
                    };
                    this.fireListener(action,params).then(function (data) {
                        console.info("操作成功!");
                    }, function (error) {
                        console.info("操作失败!");
                    });
                }
            };
        }

        NptGridApi.prototype.gridOptions = function () {
            return this._config.gridOptions;
        };

        NptGridApi.prototype.getActions = function () {
            return this._config.action;
        };

        NptGridApi.prototype.triggerAction = function (action) {
            var selectedData = this.uiGridApi.selection.getSelectedRows();
            if (selectedData.length === 0) {
                return;
            }
            if (this._handler[action.type]) {
                this._handler[action.type](action, selectedData[0], selectedData[0].$index - 1);
            } else {
                this._handler.none(action, selectedData);
            }
        };

        vm.init = function (nptGrid) {
            //创建API
            vm.nptGridApi = new NptGridApi(nptGrid);
            vm.gridOptions = vm.nptGridApi.gridOptions();
            vm.action = vm.nptGridApi.getActions();

            //观察data变化计算行号
            $scope.$watch("model", function (newValue) {
                if (newValue) {
                    var index = 1;
                    angular.forEach(newValue, function (value) {
                        value.$index = index++;
                    });
                }
            }, true);
        };

        vm.triggerAction = function (menu) {
            vm.nptGridApi.triggerAction(menu);
        };

        if ($scope.nptGrid) {
            vm.init($scope.nptGrid);
        }
    })
    .controller("editGridController", function ($uibModalInstance, formData) {
        var vm = this;
        // function assignment
        vm.ok = ok;
        vm.cancel = cancel;

        // variable assignment
        vm.formData = formData;
        vm.originalFields = angular.copy(vm.formData.fields);

        // function definition
        function ok() {
            $uibModalInstance.close(vm.formData.model);
        }

        function cancel() {
            $uibModalInstance.dismiss('cancel');
        }
    })
    .directive("nptGrid", function () {
        return {
            restrict: "EA",
            controller: "GridController as vm",
            replace: true,
            templateUrl: function (element, attrs) {
                return attrs.templateUrl || "/template/grid/npt-grid.html";
            },
            scope: {
                nptGrid: "=",
                model: "="
            }
        };
    });<commit_msg>gird菜单，如果没有action配置，则不显示‘操作’按钮<commit_after>/**
 * Created by leon on 15/11/24.
 */

angular.module("ui.neptune.directive.grid",
    ['ui.grid', "ui.grid.pagination", 'ui.grid.resizeColumns',
        'ui.grid.moveColumns', 'ui.grid.autoResize', 'ngRoute',
        'ng-context-menu', 'ui.bootstrap'])
    .provider("nptGridStore", function () {

        this.$get = function () {

            function NptGridStore(name, setting) {
                if (!name) {
                    throw new Error("npt grid store must set name.");
                }
                this._name = name;
                if (setting) {
                    this.setting(setting);
                }
            }

            NptGridStore.prototype.setting = function (setting) {
                this._gridOptions = {
                    paginationPageSizes: [10, 20, 30],
                    paginationPageSize: 10,
                    showGridFooter: true,
                    showColumnFooter: true,
                    //enableSorting: false,
                    enableRowSelection: true,
                    enableSelectAll: false,
                    multiSelect: false,
                    enableFullRowSelection: true,
                    selectionRowHeaderWidth: 35,
                    rowHeight: 35,
                    rowTemplate: "/template/grid/npt-grid-row.html"
                };
                this._gridOptions = angular.extend(setting.gridOptions, this._gridOptions);
                this._action = setting.action;
            };

            NptGridStore.prototype.gridOptions = function () {
                return this._gridOptions;
            };

            NptGridStore.prototype.action = function () {
                return this._action;
            };

            function nptGridStoreFactory(name, setting) {
                return new NptGridStore(name, setting);
            }

            return nptGridStoreFactory;
        };

    })
    .controller("GridController", function ($scope, i18nService, $q, $injector, $uibModal, nptFormStore) {
        var vm = this;

        //设置中文
        i18nService.setCurrentLang('zh-cn');

        vm.forms = {
            init: function () {
            },
            open: function (name, data) {
                this.originData = data;

                var result = $uibModal.open({
                    animation: true,
                    templateUrl: '/template/grid/grid-edit.html',
                    controller: 'editGridController',
                    controllerAs: 'vm',
                    resolve: {
                        formData: function ($q) {
                            var deferd = $q.defer();
                            nptFormStore.form(name, function (config) {
                                deferd.resolve({
                                    fields: angular.copy(config.fields), //拷贝新版本,防止修改原始配置
                                    model: angular.copy(data), //拷贝新版本,防止修改原始配置
                                    options: angular.copy(config.options) //拷贝新版本,防止修改原始配置
                                });
                            });
                            return deferd.promise;
                        }
                    }
                }).result;

                return result;
            },
            close: function () {
                $uibModal.close();
            }
        };

        function NptGridApi(nptGridOptions) {
            var self = this;
            this._options = nptGridOptions;
            this._config = {};
            if (nptGridOptions.store) {
                this._config.gridOptions = nptGridOptions.store.gridOptions() || {};
                this._config.action = nptGridOptions.store.action();
                //设置注册Api回调,如果用户在配置中设置会被替换
                this._config.gridOptions.onRegisterApi = function (uiGridApi) {
                    self.uiGridApi = uiGridApi;

                    //如果存在注册Api回调则执行
                    if (self._options.onRegisterApi) {
                        self._options.onRegisterApi(self);
                    }
                };

                //添加操作区域
                if (this._config.action) {
                    this._config.gridOptions.columnDefs.push({
                        field: 'actionScope',
                        displayName: "操作",
                        width: 100,
                        cellTemplate: "/template/grid/npt-grid-row-cell.html"
                    });
                }

                //添加序号区域,
                this._config.gridOptions.columnDefs.unshift({
                    field: '$index',
                    displayName: "序号",
                    enableSorting: true,
                    width: 60
                });

                // 增加菜单动作
                this.action = {};
                if (this._config.action) {
                    angular.forEach(this._config.action, function (action, key) {
                        self.action[key] = function () {
                            return self.triggerAction(action);
                        };

                        self.action[key].addListener = function (listener) {
                            action.listens = action.listens || [];
                            if (listener) {
                                action.listens.push(listener);
                            }
                        };
                    });
                }

                //设置数据
                this._config.gridOptions.data = "model";
            }

            // 定义框架支持的菜单操作
            this._handler = {
                fireListener:function(action, params) {
                    var deferd = $q.defer();
                    var promise = deferd.promise;
                    deferd.resolve(params);
                    var promisesArr = [promise];
                    //配置中的listen
                    angular.forEach(action.listens, function (listen) {
                        promisesArr.push($q.when($injector.invoke(listen, this, {
                            "params": params
                        })));
                    });
                    return $q.all(promisesArr);
                },
                add: function (action, item, index) {
                    var _handlerSelf = this;
                    var result = vm.forms.open(action.target, {});
                    if (result) {
                        result = result.then(function (data) {
                            var params = {
                                action: action,
                                index: index,
                                data: data
                            };

                            _handlerSelf.fireListener(action,params).then(function () {
                                //执行成功,将数据添加到表格
                                $scope.model.unshift(params.data);
                                console.info("添加执行成功.");
                            }, function (error) {
                                console.info("添加执行失败!" + JSON.stringify(error));
                            });

                        }, function (error) {
                            console.info("添加操作取消");
                        });
                        return result;
                    }
                },
                del: function (action, item, index) {
                    var params = {
                        action: action,
                        item: item,
                        index: index
                    };
                    var result = this.fireListener(action,params).then(function () {
                        $scope.model.splice(params.index, 1);
                    }, function (error) {
                        console.info("删除失败!" + error);
                    });

                    return result;
                },
                edit: function (action, item, index) {
                    var _handlerSelf = this;
                    var result = vm.forms.open(action.target, item);
                    if (result) {
                        result.then(function (data) {
                            //重新组织参数
                            var params = {
                                action: action,
                                index: index,
                                data: data,
                                item: angular.copy(item) //防止修改
                            };

                            _handlerSelf.fireListener(action,params).then(function (data) {
                                console.info("编辑操作成功!");
                                //将新数据更新到表格
                                angular.extend(item, params.data);
                            }, function (error) {
                                console.info("编辑操作失败!");
                            });

                        }, function (error) {
                            console.info("编辑操作取消");
                        });
                    }

                    return result;
                },
                none: function (action, item, index) {
                    var params = {
                        action: action,
                        item: angular.copy(item),
                        index: index
                    };
                    this.fireListener(action,params).then(function (data) {
                        console.info("操作成功!");
                    }, function (error) {
                        console.info("操作失败!");
                    });
                }
            };
        }

        NptGridApi.prototype.gridOptions = function () {
            return this._config.gridOptions;
        };

        NptGridApi.prototype.getActions = function () {
            return this._config.action;
        };

        NptGridApi.prototype.triggerAction = function (action) {
            var selectedData = this.uiGridApi.selection.getSelectedRows();
            if (selectedData.length === 0) {
                return;
            }
            if (this._handler[action.type]) {
                this._handler[action.type](action, selectedData[0], selectedData[0].$index - 1);
            } else {
                this._handler.none(action, selectedData);
            }
        };

        vm.init = function (nptGrid) {
            //创建API
            vm.nptGridApi = new NptGridApi(nptGrid);
            vm.gridOptions = vm.nptGridApi.gridOptions();
            vm.action = vm.nptGridApi.getActions();

            //观察data变化计算行号
            $scope.$watch("model", function (newValue) {
                if (newValue) {
                    var index = 1;
                    angular.forEach(newValue, function (value) {
                        value.$index = index++;
                    });
                }
            }, true);
        };

        vm.triggerAction = function (menu) {
            vm.nptGridApi.triggerAction(menu);
        };

        if ($scope.nptGrid) {
            vm.init($scope.nptGrid);
        }
    })
    .controller("editGridController", function ($uibModalInstance, formData) {
        var vm = this;
        // function assignment
        vm.ok = ok;
        vm.cancel = cancel;

        // variable assignment
        vm.formData = formData;
        vm.originalFields = angular.copy(vm.formData.fields);

        // function definition
        function ok() {
            $uibModalInstance.close(vm.formData.model);
        }

        function cancel() {
            $uibModalInstance.dismiss('cancel');
        }
    })
    .directive("nptGrid", function () {
        return {
            restrict: "EA",
            controller: "GridController as vm",
            replace: true,
            templateUrl: function (element, attrs) {
                return attrs.templateUrl || "/template/grid/npt-grid.html";
            },
            scope: {
                nptGrid: "=",
                model: "="
            }
        };
    });<|endoftext|>


<commit_before>// Prototypes

String.prototype.format = function() {
	var args = arguments;
	return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function(m, n) {
		if (m == "{{") {
			return "{";
		}
		if (m == "}}") {
			return "}";
		}
		return args[n];
	});
};

String.prototype.html = function() {
	return $('').text(this).html();
};

String.prototype.htmlWithLines = function() {
	var text = this.html();
	return text.replace(/\n/g, '
');
};

// Global functions

function loc () {
	var code = arguments[0];
	var text = l[code];
	if (text != null) {
		var params = []	;
		for (var i = 1 ; i < arguments.length ; i++) {
			params.push(arguments[i]);
		}
		return text.format (params);
	} else {
		return "##" + code + "##";
	}
}

// source: http://www.w3schools.com/js/js_cookies.asp
function getCookie(c_name) {
    var i,x,y,ARRcookies=document.cookie.split(";");
    for (i=0;i<ARRcookies.length;i++) {
        x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
        y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
        x=x.replace(/^\s+|\s+$/g,"");
        if (x==c_name) {
            return unescape(y);
        }
    }
}

// Application singleton

var Application = function () {

    function login() {
        location = 'login?callbackUrl={0}'.format(encodeURIComponent(location.href));
    }

	/**
	 * Opens a dialog and manages the submit through a AJAX request.
	 * Configuration:
	 * @param id Dialog HTML ID
	 * @param title Dialog title
	 * @param method HTTP method (defaults to 'POST')
	 * @param url URL to post to
	 * @param successFn Function to call when the submit is OK. This function takes the returned JSON data as a unique parameter
	 * @param openFn Function to call when the dialog is opended. This defaults to no action
	 * @param validateFn Function to call when validating before submitting. It must return false when
	 *    the validation is not OK. The default returns always true.
	 */
	function dialogAndSubmit (config) {
	    config = $.extend({
	        method: 'POST',
	        width: 450,
	        openFn: $.noop,
	        validateFn: function () {
	            return true;
	        }
	    }, config);
	    // Dialog
	    dialog ({
	        id: config.id,
	        title: config.title,
	        openFn: config.openFn,
	        width: config.width,
	        submitFn: function (closeFn) {
	            if (config.validateFn()) {
                    submit ({
                        id: config.id,
                        method: config.method,
                        url: config.url,
                        successFn: function (data) {
                            // Does something with the data
                            config.successFn(data);
                            // Closes the dialog
                            closeFn();
                        },
                        errorMessageFn: function (message) {
                            dialogError(config.id, message);
                        }
                    });
                }
	        }
	    });
	}

	/**
	 * Displays an error in a dialog. If the message is null or not defined, clears the error box.
	 */
	function dialogError (id, message) {
	    error(id + '-error');
	}

	/**
	 * Displays an error message in a container
	 */
	function error (id, message) {
	    if (message && message != '') {
            $('#' + id).html(message.htmlWithLines());
            $('#' + id).show();
        } else {
            $('#' + id).hide();
        }
	}

	/**
	 * Opens a dialog defined by the dialogId HTML fragment.
	 * @param id ID of a HTML section
	 * @param title Title of the dialog
	 * @param submitFn Function to call when the submit button
	 * is triggered. The submitFn is called with a function
	 * (no argument) which is responsible to close the dialog. Defaults to just
	 * closing the dialog
	 * @param openFn Function to call when the dialog is opended. This defaults to no action
	 */
	function dialog (config) {
	    config = $.extend({
	        submitFn: function (closeFn) {
	            closeFn();
	        },
	        openFn: $.noop,
	        width: 450
	    }, config);
		// Sets the submit function
		$('#' + config.id).unbind('submit');
		$('#' + config.id).submit(function () {
			config.submitFn(function () {
				$('#' + config.id).dialog('close');
			});
			return false;
		});
		// Sets the cancel button (if any)
		$('#' + config.id + '-cancel').unbind('click');
		$('#' + config.id + '-cancel').click(function () {
			$('#' + config.id).dialog('close');
		});
		// Clears any error message
		dialogError();
		// Initialization
		config.openFn();
		// Shows the dialog
		$('#' + config.id).dialog({
			title: config.title,
			width: config.width,
			modal: true
		});
	}

	/**
	 * Collects all values in a form
	 */
	function values (baseId) {
		var data = {};
		$('#' + baseId).find('input,textarea,select').each (function (index, field) {
			var name = field.getAttribute('name');
			var value = field.value;
			data[name] = value;
		});
		// console.log(JSON.stringify(data));
		return data;
	}

	/**
	 * Submit a set of data defined in a form.
	 * @param id ID of the HTML form
	 * @param method HTTP method to use ('GET', 'POST'...)
	 * @param url URL to call
	 * @param successFn (JSON => .) Function called in case of success.
	 * @param errorMessageFn (String => .) Function called in case of error.
	 * @see #ajax
	 */
	function submit (config) {
	    config = $.extend({
	        method: 'POST',
	        successFn: $.noop,
	        errorMessageFn: function (message) {
	            displayError(message);
	        }
	    }, config);
		// Collects all fields values
		var data = values (config.id);
		// Call
		ajax (config.method, config.url, data, config.successFn, config.errorMessageFn);
	}

	function onAjaxError(jqXHR, textStatus, errorThrown, errorMessageFn) {
	  	if (errorMessageFn) {
	  		if (jqXHR.status == 500 && jqXHR.responseText && jqXHR.responseText != '') {
	  			errorMessageFn(jqXHR.responseText);
	  		} else {
	  			var message = getAjaxError(loc('client.error.general'), jqXHR, textStatus, errorThrown);
	  			errorMessageFn(message);
	  		}
	  	} else {
	  		Application.displayAjaxError (loc('client.error.general'), jqXHR, textStatus, errorThrown);
	  	}
	}

	/**
	 * @param method HTTP method to use ('GET', 'POST'...)
	 * @param url URL to call
	 * @param data Data to send as JSON
	 * @param successFn (JSON => .) Function called in case of success.
	 * @param errorMessageFn (String => .) Function called in case of error.
	 */
	function ajax (method, url, data, successFn, errorMessageFn) {
		$.ajax({
			type: method,
			url: url,
			contentType: 'application/json',
			data: JSON.stringify(data),
			dataType: 'json',
			success: function (data) {
				successFn(data);
			},
			error: function (jqXHR, textStatus, errorThrown) {
				onAjaxError(jqXHR, textStatus, errorThrown, errorMessageFn);
			}
		});
	}

	function ajaxDelete (url, successFn, errorMessageFn) {
		$.ajax({
			type: 'DELETE',
			url: url,
			dataType: 'json',
			success: function (data) {
				successFn(data);
			},
			error: function (jqXHR, textStatus, errorThrown) {
				onAjaxError(jqXHR, textStatus, errorThrown, errorMessageFn);
			}
		});
	}

	function confirmAndCall (text, callback) {
		$('
{0}
'.format(text)).dialog({
			title: loc('general.confirm.title'),
			dialogClass: 'confirm-dialog',
			modal: true,
			buttons: {
				Ok: function () {
					$( this ).dialog( "close" );
					callback();
				},
				Cancel: function () {
					$( this ).dialog( "close" );
				}
			}
		});
	}

	function confirmIDAndCall (id, callback) {
		var text = document.getElementById(id).value;
		confirmAndCall(text, callback);
	}

	function displayError (text) {
		$('
{0}
'.format(text.htmlWithLines())).dialog({
			title: loc('client.error.title'),
			modal: true,
            buttons: {
                Ok: function() {
                    $( this ).dialog( "close" );
                }
            }
		});
	}

	function getAjaxError (message, jqXHR, textStatus, errorThrown) {
	    if (jqXHR.status == 0) {
            return loc('client.error.general');
	    } else {
		    return '{0}\n[{1}] {2}'.format(message, jqXHR.status, jqXHR.statusText);
		}
	}

	function displayAjaxError (message, jqXHR, textStatus, errorThrown) {
		var text = getAjaxError(message, jqXHR, textStatus, errorThrown);
		displayError(text);
	}

	function changeLanguage (lang) {
		if (location.search.indexOf("language") > -1) {
	    	location.search = location.search.replace(/language=[a-z][a-z]/, "language=" + lang);
		} else if (location.search == "") {
			var url = location.href;
			if (url.substr(url.length - 1) == '?') {
				location.href += "language=" + lang;
			} else {
				location.href += "?language=" + lang;
			}
		} else {
			location.href += "&language=" + lang;
		}
	}

	function validate (selector, test) {
		if (test) {
			$(selector).removeClass("invalid");
			return true;
		} else {
			$(selector).addClass("invalid");
			return false;
		}
	}

	function loading (selector, loading) {
		$(selector).empty();
		if (loading) {
			$(selector).append('
{0}
'.format(loc('general.loading')));
		}
	}

	function extractEntity (value) {
	    var pos = value.indexOf('/');
	    if (pos > 0) {
	        return value.substring(0, pos);
	    } else {
	        return value;
	    }
	}

	function deleteEntity (entity, id, backUrl) {
		var url = 'ui/manage/{0}/{1}'.format(entity, id);
		AJAX.get ({
			url: url,
			successFn: function (o) {
				confirmAndCall(
					loc('{0}.delete.prompt'.format(extractEntity(entity)), o.name),
					function () {
						ajaxDelete (
							url,
							function () {
								location = backUrl;
							},
							displayError);
					});
			}
	    });
	}

	function tooltips () {
        $('.tooltip-source').tooltip({
            placement: 'bottom'
        });
    }

    function informationMessageTemplate () {
        return Template.config({
            url: 'ui/info/message',
            refresh: true,
            render: Template.asSimpleTemplate('informationMessageTemplate')
        });
    }

	return {
	    login: login,
		dialog: dialog,
		submit: submit,
		values: values,
		dialogAndSubmit: dialogAndSubmit,
		dialogError: dialogError,
		error: error,
		confirmAndCall: confirmAndCall,
		confirmIDAndCall: confirmIDAndCall,
		displayError: displayError,
		displayAjaxError: displayAjaxError,
		onAjaxError: onAjaxError,
		getAjaxError: getAjaxError,
		changeLanguage: changeLanguage,
		validateTextAsName: function (selector) {
			var value = $(selector).val();
			var trimmedValue = value.trim();
			return validate (selector, trimmedValue == value && trimmedValue != "");
		},
		validateTextAsTrimmed: function (selector) {
			var value = $(selector).val();
			var trimmedValue = value.trim();
			return validate (selector, trimmedValue == value);
		},
		validateConfirmation: function (source, confirmation) {
			var value = $(source).val();
			var confirmValue = $(confirmation).val();
			return validate (confirmation, confirmValue == value);
		},
		loading: loading,
		deleteEntity: deleteEntity,
		ajax: ajax,
		tooltips: tooltips,
		informationMessageTemplate: informationMessageTemplate
	};

} ();<commit_msg>AJAX: getting rid of Application.ajax<commit_after>// Prototypes

String.prototype.format = function() {
	var args = arguments;
	return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function(m, n) {
		if (m == "{{") {
			return "{";
		}
		if (m == "}}") {
			return "}";
		}
		return args[n];
	});
};

String.prototype.html = function() {
	return $('').text(this).html();
};

String.prototype.htmlWithLines = function() {
	var text = this.html();
	return text.replace(/\n/g, '
');
};

// Global functions

function loc () {
	var code = arguments[0];
	var text = l[code];
	if (text != null) {
		var params = []	;
		for (var i = 1 ; i < arguments.length ; i++) {
			params.push(arguments[i]);
		}
		return text.format (params);
	} else {
		return "##" + code + "##";
	}
}

// source: http://www.w3schools.com/js/js_cookies.asp
function getCookie(c_name) {
    var i,x,y,ARRcookies=document.cookie.split(";");
    for (i=0;i<ARRcookies.length;i++) {
        x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
        y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
        x=x.replace(/^\s+|\s+$/g,"");
        if (x==c_name) {
            return unescape(y);
        }
    }
}

// Application singleton

var Application = function () {

    function login() {
        location = 'login?callbackUrl={0}'.format(encodeURIComponent(location.href));
    }

	/**
	 * Opens a dialog and manages the submit through a AJAX request.
	 * Configuration:
	 * @param id Dialog HTML ID
	 * @param title Dialog title
	 * @param method HTTP method (defaults to 'POST')
	 * @param url URL to post to
	 * @param successFn Function to call when the submit is OK. This function takes the returned JSON data as a unique parameter
	 * @param openFn Function to call when the dialog is opended. This defaults to no action
	 * @param validateFn Function to call when validating before submitting. It must return false when
	 *    the validation is not OK. The default returns always true.
	 */
	function dialogAndSubmit (config) {
	    config = $.extend({
	        method: 'POST',
	        width: 450,
	        openFn: $.noop,
	        validateFn: function () {
	            return true;
	        }
	    }, config);
	    // Dialog
	    dialog ({
	        id: config.id,
	        title: config.title,
	        openFn: config.openFn,
	        width: config.width,
	        submitFn: function (closeFn) {
	            if (config.validateFn()) {
                    submit ({
                        id: config.id,
                        method: config.method,
                        url: config.url,
                        successFn: function (data) {
                            // Does something with the data
                            config.successFn(data);
                            // Closes the dialog
                            closeFn();
                        },
                        errorMessageFn: function (message) {
                            dialogError(config.id, message);
                        }
                    });
                }
	        }
	    });
	}

	/**
	 * Displays an error in a dialog. If the message is null or not defined, clears the error box.
	 */
	function dialogError (id, message) {
	    error(id + '-error');
	}

	/**
	 * Displays an error message in a container
	 */
	function error (id, message) {
	    if (message && message != '') {
            $('#' + id).html(message.htmlWithLines());
            $('#' + id).show();
        } else {
            $('#' + id).hide();
        }
	}

	/**
	 * Opens a dialog defined by the dialogId HTML fragment.
	 * @param id ID of a HTML section
	 * @param title Title of the dialog
	 * @param submitFn Function to call when the submit button
	 * is triggered. The submitFn is called with a function
	 * (no argument) which is responsible to close the dialog. Defaults to just
	 * closing the dialog
	 * @param openFn Function to call when the dialog is opended. This defaults to no action
	 */
	function dialog (config) {
	    config = $.extend({
	        submitFn: function (closeFn) {
	            closeFn();
	        },
	        openFn: $.noop,
	        width: 450
	    }, config);
		// Sets the submit function
		$('#' + config.id).unbind('submit');
		$('#' + config.id).submit(function () {
			config.submitFn(function () {
				$('#' + config.id).dialog('close');
			});
			return false;
		});
		// Sets the cancel button (if any)
		$('#' + config.id + '-cancel').unbind('click');
		$('#' + config.id + '-cancel').click(function () {
			$('#' + config.id).dialog('close');
		});
		// Clears any error message
		dialogError();
		// Initialization
		config.openFn();
		// Shows the dialog
		$('#' + config.id).dialog({
			title: config.title,
			width: config.width,
			modal: true
		});
	}

	/**
	 * Collects all values in a form
	 */
	function values (baseId) {
		var data = {};
		$('#' + baseId).find('input,textarea,select').each (function (index, field) {
			var name = field.getAttribute('name');
			var value = field.value;
			data[name] = value;
		});
		// console.log(JSON.stringify(data));
		return data;
	}

	/**
	 * Submit a set of data defined in a form.
	 * @param id ID of the HTML form
	 * @param method HTTP method to use ('GET', 'POST'...)
	 * @param url URL to call
	 * @param successFn (JSON => .) Function called in case of success.
	 * @param errorMessageFn (String => .) Function called in case of error.
	 * @see #ajax
	 */
	function submit (config) {
	    config = $.extend({
	        method: 'POST',
	        successFn: $.noop,
	        errorMessageFn: function (message) {
	            displayError(message);
	        }
	    }, config);
		// Collects all fields values
		var data = values (config.id);
		// Call
		AJAX.call({
		   method: config.method,
		   url: config.url,
		   data: data,
		   successFn: config.successFn,
		   errorFn: AJAX.simpleAjaxErrorFn(config.errorMessageFn)
		});
	}

	function onAjaxError(jqXHR, textStatus, errorThrown, errorMessageFn) {
	  	if (errorMessageFn) {
	  		if (jqXHR.status == 500 && jqXHR.responseText && jqXHR.responseText != '') {
	  			errorMessageFn(jqXHR.responseText);
	  		} else {
	  			var message = getAjaxError(loc('client.error.general'), jqXHR, textStatus, errorThrown);
	  			errorMessageFn(message);
	  		}
	  	} else {
	  		Application.displayAjaxError (loc('client.error.general'), jqXHR, textStatus, errorThrown);
	  	}
	}

	/**
	 * @param method HTTP method to use ('GET', 'POST'...)
	 * @param url URL to call
	 * @param data Data to send as JSON
	 * @param successFn (JSON => .) Function called in case of success.
	 * @param errorMessageFn (String => .) Function called in case of error.
	 */
	function ajax (method, url, data, successFn, errorMessageFn) {
		$.ajax({
			type: method,
			url: url,
			contentType: 'application/json',
			data: JSON.stringify(data),
			dataType: 'json',
			success: function (data) {
				successFn(data);
			},
			error: function (jqXHR, textStatus, errorThrown) {
				onAjaxError(jqXHR, textStatus, errorThrown, errorMessageFn);
			}
		});
	}

	function ajaxDelete (url, successFn, errorMessageFn) {
		$.ajax({
			type: 'DELETE',
			url: url,
			dataType: 'json',
			success: function (data) {
				successFn(data);
			},
			error: function (jqXHR, textStatus, errorThrown) {
				onAjaxError(jqXHR, textStatus, errorThrown, errorMessageFn);
			}
		});
	}

	function confirmAndCall (text, callback) {
		$('
{0}
'.format(text)).dialog({
			title: loc('general.confirm.title'),
			dialogClass: 'confirm-dialog',
			modal: true,
			buttons: {
				Ok: function () {
					$( this ).dialog( "close" );
					callback();
				},
				Cancel: function () {
					$( this ).dialog( "close" );
				}
			}
		});
	}

	function confirmIDAndCall (id, callback) {
		var text = document.getElementById(id).value;
		confirmAndCall(text, callback);
	}

	function displayError (text) {
		$('
{0}
'.format(text.htmlWithLines())).dialog({
			title: loc('client.error.title'),
			modal: true,
            buttons: {
                Ok: function() {
                    $( this ).dialog( "close" );
                }
            }
		});
	}

	function getAjaxError (message, jqXHR, textStatus, errorThrown) {
	    if (jqXHR.status == 0) {
            return loc('client.error.general');
	    } else {
		    return '{0}\n[{1}] {2}'.format(message, jqXHR.status, jqXHR.statusText);
		}
	}

	function displayAjaxError (message, jqXHR, textStatus, errorThrown) {
		var text = getAjaxError(message, jqXHR, textStatus, errorThrown);
		displayError(text);
	}

	function changeLanguage (lang) {
		if (location.search.indexOf("language") > -1) {
	    	location.search = location.search.replace(/language=[a-z][a-z]/, "language=" + lang);
		} else if (location.search == "") {
			var url = location.href;
			if (url.substr(url.length - 1) == '?') {
				location.href += "language=" + lang;
			} else {
				location.href += "?language=" + lang;
			}
		} else {
			location.href += "&language=" + lang;
		}
	}

	function validate (selector, test) {
		if (test) {
			$(selector).removeClass("invalid");
			return true;
		} else {
			$(selector).addClass("invalid");
			return false;
		}
	}

	function loading (selector, loading) {
		$(selector).empty();
		if (loading) {
			$(selector).append('
{0}
'.format(loc('general.loading')));
		}
	}

	function extractEntity (value) {
	    var pos = value.indexOf('/');
	    if (pos > 0) {
	        return value.substring(0, pos);
	    } else {
	        return value;
	    }
	}

	function deleteEntity (entity, id, backUrl) {
		var url = 'ui/manage/{0}/{1}'.format(entity, id);
		AJAX.get ({
			url: url,
			successFn: function (o) {
				confirmAndCall(
					loc('{0}.delete.prompt'.format(extractEntity(entity)), o.name),
					function () {
						ajaxDelete (
							url,
							function () {
								location = backUrl;
							},
							displayError);
					});
			}
	    });
	}

	function tooltips () {
        $('.tooltip-source').tooltip({
            placement: 'bottom'
        });
    }

    function informationMessageTemplate () {
        return Template.config({
            url: 'ui/info/message',
            refresh: true,
            render: Template.asSimpleTemplate('informationMessageTemplate')
        });
    }

	return {
	    login: login,
		dialog: dialog,
		submit: submit,
		values: values,
		dialogAndSubmit: dialogAndSubmit,
		dialogError: dialogError,
		error: error,
		confirmAndCall: confirmAndCall,
		confirmIDAndCall: confirmIDAndCall,
		displayError: displayError,
		displayAjaxError: displayAjaxError,
		onAjaxError: onAjaxError,
		getAjaxError: getAjaxError,
		changeLanguage: changeLanguage,
		validateTextAsName: function (selector) {
			var value = $(selector).val();
			var trimmedValue = value.trim();
			return validate (selector, trimmedValue == value && trimmedValue != "");
		},
		validateTextAsTrimmed: function (selector) {
			var value = $(selector).val();
			var trimmedValue = value.trim();
			return validate (selector, trimmedValue == value);
		},
		validateConfirmation: function (source, confirmation) {
			var value = $(source).val();
			var confirmValue = $(confirmation).val();
			return validate (confirmation, confirmValue == value);
		},
		loading: loading,
		deleteEntity: deleteEntity,
		ajax: ajax,
		tooltips: tooltips,
		informationMessageTemplate: informationMessageTemplate
	};

} ();<|endoftext|>


<commit_before>;
/**
 * This is a stylesheet loader which is useful for controlling the dynamic loading of
 * CSS. It provides the following features:
 * - load CSS from a string or a stylesheet URL
 * - returns a promise representing whether the CSS has loaded
 * Inspired by the Filament Group's work on CSS loading: https://github.com/filamentgroup/loadCSS
 */

(function(window) {
    'use strict';

    // Create an array for storing our stylesheet promises.
    var stylesheetsLoaded = [];

    /**
     * Load the supplied stylesheet url at the end of the <body>
     * @param  {string} url         The stylesheet URL to load
     * @param  {string} stylesClass An optional class that we'll attach to the
     *                              rendered <style> element.
     * @param  {string} media       The media to which we wish the stylesheet to apply.
     *                              Defaults to 'all'.
     * @return {window.Promise}     The promise representing whether the stylesheet has loaded.
     */
    function _loadStylesheetFromURL(url, stylesClass, media) {
        // https://github.com/filamentgroup/loadCSS/blob/master/src/loadCSS.js
        var stylesheetPromise = new window.Promise(function(resolve, reject) {

            var doc = window.document;
            var refs = (doc.body || doc.getElementsByTagName('head')[0]).childNodes;
            var ref = refs[ refs.length - 1];

            // Create a new link tag
            var link = document.createElement('link');

            // Use the url argument as source attribute
            link.rel = 'stylesheet';
            link.href = url;

            // Temporarily set media to something inapplicable to ensure
            // it'll fetch without blocking render
            link.media = 'only x';

            if (stylesClass) {
                link.classList.add(stylesClass);
            }

            function loadCB() {
                if (link.addEventListener) {
                    link.removeEventListener( 'load', loadCB );
                }

                link.media = media || 'all';
                resolve(url);
            }

            // Call resolve when it’s loaded
            if (link.addEventListener) {
                link.addEventListener('load', function() {
                    loadCB();
                }, false);
            } else {
                link.attachEvent('load', function() {
                    loadCB();
                });
            }

            // Reject the promise if there’s an error
            if (link.addEventListener) {
                link.addEventListener('error', function() {
                    reject(url);
                }, false);
            } else {
                link.attachEvent('error', function() {
                    reject(url);
                });
            }

            // wait until body is defined before injecting link. This ensures a non-blocking load in IE11.
            function ready(cb) {
                if (doc.body) {
                    return cb();
                }
                setTimeout(function() {
                    ready(cb);
                });
            }

            // Inject link
            // Note: `insertBefore` is used instead of `appendChild`, for safety
            // re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
            ready(function() {
                ref.parentNode.insertBefore(link, ref.nextSibling);
            });
        });

        // Add our script to the stylesheetsLoaded array so other parts of the application
        // can determine whether a given stylesheet is / has been script loaded.
        stylesheetsLoaded.push({
            urlOrStyles: url,
            promise: stylesheetPromise,
        });

        return stylesheetPromise;
    }

    /**
     * Load the supplied styles into the head.
     * @param  {string} styles      The styles we wish to load
     * @param  {string} stylesClass An optional class that we'll attach to the
     *                              rendered <style> element.
     * @param  {string} media       The media to which we wish the stylesheet to apply.
     *                              Defaults to 'all'.
     * @return {window.Promise}     A promise that indicates when the styles are available.
     */
    function _loadStylesheetFromCSS(styles, stylesClass, media) {
        var stylesheetPromise = new window.Promise(function(resolve, reject) {

            var doc = window.document;
            var refs = (doc.body || doc.getElementsByTagName('head')[0]).childNodes;
            var ref = refs[ refs.length - 1];

            var stylesheet = document.createElement('style');

            try {
                stylesheet.type = 'text/css';

                if (stylesheet.styleSheet) {
                    stylesheet.styleSheet.cssText = styles;
                } else {
                    stylesheet.appendChild(document.createTextNode(styles));
                }

                if (stylesClass) {
                    stylesheet.classList.add(stylesClass);
                }

                stylesheet.media = media || 'all';
                ref.parentNode.insertBefore(stylesheet, ref.nextSibling);

                resolve(styles);
            } catch (error) {
                reject(error, styles);
            }
        });

        // Add our script to the stylesheetsLoaded array so other parts of the application
        // can determine whether a given stylesheet is / has been script loaded.
        stylesheetsLoaded.push({
            urlOrStyles: styles,
            promise: stylesheetPromise,
        });

        return stylesheetPromise;
    }

    /**
     * Return the promise of the supplied stylesheet.
     * If the stylesheet has not already been loaded, force the load.
     * @param  {string} urlOrStyles  A script URL
     * @param  {string} stylesClass  An optional class that we'll attach to the
     *                               rendered <style> element.
     * @param  {string} media        The media to which we wish the stylesheet to apply.
     *                               Defaults to 'all'.
     * @return {window.Promise}      The promise representing whether the stylesheet has loaded.
     */
    var ffxnzLoadStylesheet = function(urlOrStyles, stylesClass, media) {
        var promise;

        var stylesheets = stylesheetsLoaded.filter(function(item) {
            return item.urlOrStyles === urlOrStyles;
        });

        if (stylesheets.length) {
            promise = stylesheets[0].promise;
        } else {
            // Our stylesheet hasn't been previously requested for load,
            // so let's determine if it's a URL or a string of CSS and load it.
            if (/\.css$/.test(urlOrStyles)) {
                promise = _loadStylesheetFromURL(urlOrStyles, stylesClass, media);
            } else {
                promise = _loadStylesheetFromCSS(urlOrStyles, stylesClass, media);
            }
        }

        return promise;
    };

    if (typeof module !== 'undefined' && module.exports) {
        // commonjs
        exports = module.exports = ffxnzLoadStylesheet;
    } else {
        // browserland
        window.nz = window.nz || {};
        window.nz.ffx = window.nz.ffx || {};
        window.nz.ffx.loadStylesheet = ffxnzLoadStylesheet;
        window.nz.ffx.stylesheetsLoaded = stylesheetsLoaded;
    }

}(typeof global !== 'undefined' ? global : this));
<commit_msg>Ensure we can deal with stylesheet urls with query strings<commit_after>;
/**
 * This is a stylesheet loader which is useful for controlling the dynamic loading of
 * CSS. It provides the following features:
 * - load CSS from a string or a stylesheet URL
 * - returns a promise representing whether the CSS has loaded
 * Inspired by the Filament Group's work on CSS loading: https://github.com/filamentgroup/loadCSS
 */

(function(window) {
    'use strict';

    // Create an array for storing our stylesheet promises.
    var stylesheetsLoaded = [];

    /**
     * Load the supplied stylesheet url at the end of the <body>
     * @param  {string} url         The stylesheet URL to load
     * @param  {string} stylesClass An optional class that we'll attach to the
     *                              rendered <style> element.
     * @param  {string} media       The media to which we wish the stylesheet to apply.
     *                              Defaults to 'all'.
     * @return {window.Promise}     The promise representing whether the stylesheet has loaded.
     */
    function _loadStylesheetFromURL(url, stylesClass, media) {
        // https://github.com/filamentgroup/loadCSS/blob/master/src/loadCSS.js
        var stylesheetPromise = new window.Promise(function(resolve, reject) {

            var doc = window.document;
            var refs = (doc.body || doc.getElementsByTagName('head')[0]).childNodes;
            var ref = refs[ refs.length - 1];

            // Create a new link tag
            var link = document.createElement('link');

            // Use the url argument as source attribute
            link.rel = 'stylesheet';
            link.href = url;

            // Temporarily set media to something inapplicable to ensure
            // it'll fetch without blocking render
            link.media = 'only x';

            if (stylesClass) {
                link.classList.add(stylesClass);
            }

            function loadCB() {
                if (link.addEventListener) {
                    link.removeEventListener( 'load', loadCB );
                }

                link.media = media || 'all';
                resolve(url);
            }

            // Call resolve when it’s loaded
            if (link.addEventListener) {
                link.addEventListener('load', function() {
                    loadCB();
                }, false);
            } else {
                link.attachEvent('load', function() {
                    loadCB();
                });
            }

            // Reject the promise if there’s an error
            if (link.addEventListener) {
                link.addEventListener('error', function() {
                    reject(url);
                }, false);
            } else {
                link.attachEvent('error', function() {
                    reject(url);
                });
            }

            // wait until body is defined before injecting link. This ensures a non-blocking load in IE11.
            function ready(cb) {
                if (doc.body) {
                    return cb();
                }
                setTimeout(function() {
                    ready(cb);
                });
            }

            // Inject link
            // Note: `insertBefore` is used instead of `appendChild`, for safety
            // re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
            ready(function() {
                ref.parentNode.insertBefore(link, ref.nextSibling);
            });
        });

        // Add our script to the stylesheetsLoaded array so other parts of the application
        // can determine whether a given stylesheet is / has been script loaded.
        stylesheetsLoaded.push({
            urlOrStyles: url,
            promise: stylesheetPromise,
        });

        return stylesheetPromise;
    }

    /**
     * Load the supplied styles into the head.
     * @param  {string} styles      The styles we wish to load
     * @param  {string} stylesClass An optional class that we'll attach to the
     *                              rendered <style> element.
     * @param  {string} media       The media to which we wish the stylesheet to apply.
     *                              Defaults to 'all'.
     * @return {window.Promise}     A promise that indicates when the styles are available.
     */
    function _loadStylesheetFromCSS(styles, stylesClass, media) {
        var stylesheetPromise = new window.Promise(function(resolve, reject) {

            var doc = window.document;
            var refs = (doc.body || doc.getElementsByTagName('head')[0]).childNodes;
            var ref = refs[ refs.length - 1];

            var stylesheet = document.createElement('style');

            try {
                stylesheet.type = 'text/css';

                if (stylesheet.styleSheet) {
                    stylesheet.styleSheet.cssText = styles;
                } else {
                    stylesheet.appendChild(document.createTextNode(styles));
                }

                if (stylesClass) {
                    stylesheet.classList.add(stylesClass);
                }

                stylesheet.media = media || 'all';
                ref.parentNode.insertBefore(stylesheet, ref.nextSibling);

                resolve(styles);
            } catch (error) {
                reject(error, styles);
            }
        });

        // Add our script to the stylesheetsLoaded array so other parts of the application
        // can determine whether a given stylesheet is / has been script loaded.
        stylesheetsLoaded.push({
            urlOrStyles: styles,
            promise: stylesheetPromise,
        });

        return stylesheetPromise;
    }

    /**
     * Return the promise of the supplied stylesheet.
     * If the stylesheet has not already been loaded, force the load.
     * @param  {string} urlOrStyles  A script URL
     * @param  {string} stylesClass  An optional class that we'll attach to the
     *                               rendered <style> element.
     * @param  {string} media        The media to which we wish the stylesheet to apply.
     *                               Defaults to 'all'.
     * @return {window.Promise}      The promise representing whether the stylesheet has loaded.
     */
    var ffxnzLoadStylesheet = function(urlOrStyles, stylesClass, media) {
        var promise;

        var stylesheets = stylesheetsLoaded.filter(function(item) {
            return item.urlOrStyles === urlOrStyles;
        });

        if (stylesheets.length) {
            promise = stylesheets[0].promise;
        } else {
            // Our stylesheet hasn't been previously requested for load,
            // so let's determine if it's a URL or a string of CSS and load it.
            if (/\.css(?:\?[^\?]+)*$/.test(urlOrStyles)) {
                promise = _loadStylesheetFromURL(urlOrStyles, stylesClass, media);
            } else {
                promise = _loadStylesheetFromCSS(urlOrStyles, stylesClass, media);
            }
        }

        return promise;
    };

    if (typeof module !== 'undefined' && module.exports) {
        // commonjs
        exports = module.exports = ffxnzLoadStylesheet;
    } else {
        // browserland
        window.nz = window.nz || {};
        window.nz.ffx = window.nz.ffx || {};
        window.nz.ffx.loadStylesheet = ffxnzLoadStylesheet;
        window.nz.ffx.stylesheetsLoaded = stylesheetsLoaded;
    }

}(typeof global !== 'undefined' ? global : this));
<|endoftext|>


<commit_before>"use strict";

/**
 * Tab dual container is a dual container with tabgroups.
 *
 * @class TabDualContainer
 * @extends {Element, TabDual}
 */
function TabDualContainer(parent)
{
	DualContainer.call(this, parent);
}

TabDualContainer.prototype = Object.create(DualContainer.prototype);

//Update all tabs object data
TabDualContainer.prototype.updateMetadata = function()
{
	this.elementA.updateMetadata();
	this.elementB.updateMetadata();
};

//Update all tab object views
TabDualContainer.prototype.updateObjectsView = function()
{
	this.elementA.updateObjectsView();
	this.elementB.updateObjectsView();
};

//Update all tab object views
TabDualContainer.prototype.updateSelection = function()
{
	this.elementA.updateSelection();
	this.elementB.updateSelection();
};

//Update all tab object views
TabDualContainer.prototype.updateSettings = function()
{
	this.elementA.updateSettings();
	this.elementB.updateSettings();
};

//Get actual tab
TabDualContainer.prototype.getActiveTab = function()
{
	var active = [];

	if(this.elementA instanceof TabGroup)
	{
		var tab = this.elementA.getActiveTab();
		if(tab !== null)
		{
			active.push(tab);
		}
	}
	else
	{
		active = active.concat(this.elementA.getActiveTab());
	}

	if(this.elementB instanceof TabGroup)
	{
		var tab = this.elementB.getActiveTab();
		if(tab !== null)
		{
			active.push(tab);
		}
		this.elementA.getActiveTab();
	}
	else
	{
		active = active.concat(this.elementB.getActiveTab());
	}

	return active;
};

//Close actual tab if it's closeable
TabDualContainer.prototype.closeActual = function()
{
	if(!(this.elementA instanceof TabGroup) || this.elementA.focused)
	{
		this.elementA.closeActual();
	}

	if(!(this.elementB instanceof TabGroup) || this.elementB.focused)
	{
		this.elementB.closeActual();
	}
};

//Select tab
TabDualContainer.prototype.selectTab = function(tab)
{
	this.elementA.selectTab(tab);
	this.elementB.selectTab(tab);
};

//Select next tab
TabDualContainer.prototype.selectNextTab = function()
{
	if(!(this.elementA instanceof TabGroup) || this.elementA.focused)
	{
		this.elementA.selectNextTab();
	}

	if(!(this.elementB instanceof TabGroup) || this.elementB.focused)
	{
		this.elementB.selectNextTab();
	}
};

//Select previous tab
TabDualContainer.prototype.selectPreviousTab = function()
{
	if(!(this.elementA instanceof TabGroup) || this.elementA.focused)
	{
		this.elementA.selectPreviousTab();
	}

	if(!(this.elementB instanceof TabGroup) || this.elementB.focused)
	{
		this.elementB.selectPreviousTab();
	}
};

//Add new option to tab group
TabDualContainer.prototype.addTab = function(TabConstructor, closeable)
{
	var tab = this.elementA.addTab(TabConstructor, closeable);
	if(tab === null)
	{
		tab = this.elementB.addTab(TabConstructor, closeable);
	}

	return tab;
};

//Get tab from tab type and attached object is there is any
TabDualContainer.prototype.getTab = function(type, obj)
{
	var tab = this.elementA.getTab(type, obj);
	if(tab === null)
	{
		tab = this.elementB.getTab(type, obj);
	}

	return tab;
};

//Remove all tabs
TabDualContainer.prototype.clear = function(forceAll)
{
	this.elementA.clear(forceAll);
	this.elementB.clear(forceAll);
};
<commit_msg>Fix dual tab visibility<commit_after>"use strict";

/**
 * Tab dual container is a dual container with tabgroups.
 *
 * @class TabDualContainer
 * @extends {Element, TabDual}
 */
function TabDualContainer(parent)
{
	DualContainer.call(this, parent);

	this.element.style.overflow = "visible";
}

TabDualContainer.prototype = Object.create(DualContainer.prototype);

//Update all tabs object data
TabDualContainer.prototype.updateMetadata = function()
{
	this.elementA.updateMetadata();
	this.elementB.updateMetadata();
};

//Update all tab object views
TabDualContainer.prototype.updateObjectsView = function()
{
	this.elementA.updateObjectsView();
	this.elementB.updateObjectsView();
};

//Update all tab object views
TabDualContainer.prototype.updateSelection = function()
{
	this.elementA.updateSelection();
	this.elementB.updateSelection();
};

//Update all tab object views
TabDualContainer.prototype.updateSettings = function()
{
	this.elementA.updateSettings();
	this.elementB.updateSettings();
};

//Get actual tab
TabDualContainer.prototype.getActiveTab = function()
{
	var active = [];

	if(this.elementA instanceof TabGroup)
	{
		var tab = this.elementA.getActiveTab();
		if(tab !== null)
		{
			active.push(tab);
		}
	}
	else
	{
		active = active.concat(this.elementA.getActiveTab());
	}

	if(this.elementB instanceof TabGroup)
	{
		var tab = this.elementB.getActiveTab();
		if(tab !== null)
		{
			active.push(tab);
		}
		this.elementA.getActiveTab();
	}
	else
	{
		active = active.concat(this.elementB.getActiveTab());
	}

	return active;
};

//Close actual tab if it's closeable
TabDualContainer.prototype.closeActual = function()
{
	if(!(this.elementA instanceof TabGroup) || this.elementA.focused)
	{
		this.elementA.closeActual();
	}

	if(!(this.elementB instanceof TabGroup) || this.elementB.focused)
	{
		this.elementB.closeActual();
	}
};

//Select tab
TabDualContainer.prototype.selectTab = function(tab)
{
	this.elementA.selectTab(tab);
	this.elementB.selectTab(tab);
};

//Select next tab
TabDualContainer.prototype.selectNextTab = function()
{
	if(!(this.elementA instanceof TabGroup) || this.elementA.focused)
	{
		this.elementA.selectNextTab();
	}

	if(!(this.elementB instanceof TabGroup) || this.elementB.focused)
	{
		this.elementB.selectNextTab();
	}
};

//Select previous tab
TabDualContainer.prototype.selectPreviousTab = function()
{
	if(!(this.elementA instanceof TabGroup) || this.elementA.focused)
	{
		this.elementA.selectPreviousTab();
	}

	if(!(this.elementB instanceof TabGroup) || this.elementB.focused)
	{
		this.elementB.selectPreviousTab();
	}
};

//Add new option to tab group
TabDualContainer.prototype.addTab = function(TabConstructor, closeable)
{
	var tab = this.elementA.addTab(TabConstructor, closeable);
	if(tab === null)
	{
		tab = this.elementB.addTab(TabConstructor, closeable);
	}

	return tab;
};

//Get tab from tab type and attached object is there is any
TabDualContainer.prototype.getTab = function(type, obj)
{
	var tab = this.elementA.getTab(type, obj);
	if(tab === null)
	{
		tab = this.elementB.getTab(type, obj);
	}

	return tab;
};

//Remove all tabs
TabDualContainer.prototype.clear = function(forceAll)
{
	this.elementA.clear(forceAll);
	this.elementB.clear(forceAll);
};
<|endoftext|>


<commit_before>import { fun, arr, obj, promise, string } from "lively.lang";
import { pt, Color, Rectangle } from "lively.graphics";
import { morph, Morph, Window, show } from "../index.js";
import { FilterableList } from "../list.js";
import Browser from "./js/browser/index.js";
import { connect, disconnectAll } from "lively.bindings"

export async function doSearch(livelySystem, searchTerm, excludes = [/systemjs-plugin-babel/]) {
  if (searchTerm.length <= 2) { return []; }

  var searchResult = await livelySystem.searchInAllPackages(searchTerm, {excludedModules: excludes});

  var [errors, found] = arr.partition(searchResult, ({isError}) => isError)

  if (errors.length) {
    show(`Errors in search results:\n${arr.pluck(errors, "value").join("\n")}`);
  }

  return found.reduce((result, ea) => {
    var nameAndLine = `${ea.packageName}${ea.pathInPackage.replace(/^\./, "")}:${ea.line}`;
    result.maxModuleNameLength = Math.max(result.maxModuleNameLength, nameAndLine.length);
    result.items.push({
      isListItem: true,
      value: ea,
      get string() {
        return nameAndLine
             + string.pad(ea.lineString, result.maxModuleNameLength - nameAndLine.length, true);
      }
    });
    return result;
  }, {items: [], maxModuleNameLength: 0}).items;

}

export class CodeSearcher extends FilterableList {

  static inWindow(props = {title: "code search", targetBrowser: null, backend: null}) {
    var searcher = new this(props);
    return new Window({
      ...obj.dissoc(props, ["targetBrowser", "backend"]),
      extent: searcher.extent.addXY(0, 25),
      targetMorph: searcher
    });
  }

  constructor(props = {}) {
    super({
      extent: pt(800,500),
      fontFamily: "Inconsolata, monospace",
      fontSize: 14,
      historyId: "lively.morphic-code searcher",
      ...obj.dissoc(props, "targetBrowser", "backend")});
    if (props.targetBrowser)
      this.state.targetBrowser = props.targetBrowser.id;
    this.state.currentSearchTerm = "";
    this.state.currentFilters = "";
    this.state.backend = props.backend || "local";
    connect(this, "accepted", this, "openBrowserForSelection");
  }

  get browser() {
    var w = this.world();
    if (!w || !this.state.targetBrowser) return null;
    return w.getMorphWithId(this.state.targetBrowser);
  }

  get backend() {
    var browser = this.browser;
    return browser ? browser.backend : this.state.backend;
  }

  set backend(backend) {
    var browser = this.browser;
    if (browser) browser.backend = backend;
    else this.state.backend = backend;
  }

  async getLivelySystem() {
    var backend = this.backend,
        remote = backend && backend !== "local" ? backend : null,
        systemInterface = await System.import("lively-system-interface");
    return remote ?
      systemInterface.serverInterfaceFor(remote) :
      systemInterface.localInterface; // FIXME
  }

  updateFilter() {
    // debounce

    var searchInput = this.get('input').textString;
    if (searchInput.length <= 2) return;

    // if (!this.typingIndicator) {
    //   this.typingIndicator = lively.ide.withLoadingIndicatorDo("input...");
    // }

    fun.debounceNamed(this.id + "updateFilterDebounced", 1200, async (needle) => {
      // if (this.typingIndicator) this.typingIndicator.then(i => i.remove());
      // this.typingIndicator = null;
      try {
        await this.searchAndUpdate(needle);
      } catch(err) {
        this.world().logError(err);
      }
    })(searchInput);
  }

  async searchAndUpdate(searchInput) {
    this.get("input").acceptInput(); // for history
    var filterTokens = searchInput.split(/\s+/).filter(ea => !!ea);

    var win = this.getWindow();
    if (win && win.targetMorph === this)
      win.title = `${win.title.split("–")[0].trim()} – ${filterTokens.join(" + ")}`;

    var searchTerm = filterTokens.shift(),
        newSearch = searchTerm != this.state.currentSearchTerm;
    if (newSearch) {
      this.state.currentSearchTerm = searchTerm;
      this.items = await doSearch(await this.getLivelySystem(), searchTerm);
    }

    filterTokens = filterTokens.map(ea => ea.toLowerCase());
    if (newSearch || this.state.currentFilters !== filterTokens.join("+")) {
      this.state.currentFilters = filterTokens.join("+");
      var filteredItems = this.state.allItems.filter(item =>
        filterTokens.every(token => item.string.toLowerCase().includes(token)))
      this.get('list').items = filteredItems;
    }
  }

  async openBrowserForSelection() {
    if (!this.selection) return;
    var {browser, selection: {column, line, packageName, pathInPackage}} = this,
        browserOrProps = browser,
        browser = await Browser.browse(
          packageName,
          pathInPackage.replace(/^\.\//, ""),
          {column, row: line-1}, browserOrProps,
          browser? browser.backend : this.backend);
    browser.state.associatedSearchPanel = this;
    return browser.activate();
  }

}
<commit_msg>select search input of code searcher on activation<commit_after>import { fun, arr, obj, promise, string } from "lively.lang";
import { pt, Color, Rectangle } from "lively.graphics";
import { morph, Morph, Window, show } from "../index.js";
import { FilterableList } from "../list.js";
import Browser from "./js/browser/index.js";
import { connect, disconnectAll } from "lively.bindings"

export async function doSearch(livelySystem, searchTerm, excludes = [/systemjs-plugin-babel/]) {
  if (searchTerm.length <= 2) { return []; }

  var searchResult = await livelySystem.searchInAllPackages(searchTerm, {excludedModules: excludes});

  var [errors, found] = arr.partition(searchResult, ({isError}) => isError)

  if (errors.length) {
    show(`Errors in search results:\n${arr.pluck(errors, "value").join("\n")}`);
  }

  return found.reduce((result, ea) => {
    var nameAndLine = `${ea.packageName}${ea.pathInPackage.replace(/^\./, "")}:${ea.line}`;
    result.maxModuleNameLength = Math.max(result.maxModuleNameLength, nameAndLine.length);
    result.items.push({
      isListItem: true,
      value: ea,
      get string() {
        return nameAndLine
             + string.pad(ea.lineString, result.maxModuleNameLength - nameAndLine.length, true);
      }
    });
    return result;
  }, {items: [], maxModuleNameLength: 0}).items;

}

export class CodeSearcher extends FilterableList {

  static inWindow(props = {title: "code search", targetBrowser: null, backend: null}) {
    var searcher = new this(props),
        win = new Window({
          ...obj.dissoc(props, ["targetBrowser", "backend"]),
          extent: searcher.extent.addXY(0, 25),
          targetMorph: searcher
        });
    connect(win, 'windowActivated', searcher, 'onWindowActivated');
    return win;
  }

  constructor(props = {}) {
    super({
      extent: pt(800,500),
      fontFamily: "Inconsolata, monospace",
      fontSize: 14,
      historyId: "lively.morphic-code searcher",
      ...obj.dissoc(props, "targetBrowser", "backend")});
    if (props.targetBrowser)
      this.state.targetBrowser = props.targetBrowser.id;
    this.state.currentSearchTerm = "";
    this.state.currentFilters = "";
    this.state.backend = props.backend || "local";
    connect(this, "accepted", this, "openBrowserForSelection");
  }

  get browser() {
    var w = this.world();
    if (!w || !this.state.targetBrowser) return null;
    return w.getMorphWithId(this.state.targetBrowser);
  }

  get backend() {
    var browser = this.browser;
    return browser ? browser.backend : this.state.backend;
  }

  set backend(backend) {
    var browser = this.browser;
    if (browser) browser.backend = backend;
    else this.state.backend = backend;
  }

  async getLivelySystem() {
    var backend = this.backend,
        remote = backend && backend !== "local" ? backend : null,
        systemInterface = await System.import("lively-system-interface");
    return remote ?
      systemInterface.serverInterfaceFor(remote) :
      systemInterface.localInterface; // FIXME
  }

  updateFilter() {
    // debounce

    var searchInput = this.get('input').textString;
    if (searchInput.length <= 2) return;

    // if (!this.typingIndicator) {
    //   this.typingIndicator = lively.ide.withLoadingIndicatorDo("input...");
    // }

    fun.debounceNamed(this.id + "updateFilterDebounced", 1200, async (needle) => {
      // if (this.typingIndicator) this.typingIndicator.then(i => i.remove());
      // this.typingIndicator = null;
      try {
        await this.searchAndUpdate(needle);
      } catch(err) {
        this.world().logError(err);
      }
    })(searchInput);
  }

  async searchAndUpdate(searchInput) {
    this.get("input").acceptInput(); // for history
    var filterTokens = searchInput.split(/\s+/).filter(ea => !!ea);

    var win = this.getWindow();
    if (win && win.targetMorph === this)
      win.title = `${win.title.split("–")[0].trim()} – ${filterTokens.join(" + ")}`;

    var searchTerm = filterTokens.shift(),
        newSearch = searchTerm != this.state.currentSearchTerm;
    if (newSearch) {
      this.state.currentSearchTerm = searchTerm;
      this.items = await doSearch(await this.getLivelySystem(), searchTerm);
    }

    filterTokens = filterTokens.map(ea => ea.toLowerCase());
    if (newSearch || this.state.currentFilters !== filterTokens.join("+")) {
      this.state.currentFilters = filterTokens.join("+");
      var filteredItems = this.state.allItems.filter(item =>
        filterTokens.every(token => item.string.toLowerCase().includes(token)))
      this.get('list').items = filteredItems;
    }
  }

  async openBrowserForSelection() {
    if (!this.selection) return;
    var {browser, selection: {column, line, packageName, pathInPackage}} = this,
        browserOrProps = browser,
        browser = await Browser.browse(
          packageName,
          pathInPackage.replace(/^\.\//, ""),
          {column, row: line-1}, browserOrProps,
          browser? browser.backend : this.backend);
    browser.state.associatedSearchPanel = this;
    return browser.activate();
  }

  onWindowActivated() {
    this.get("input").selectAll();
  }
}
<|endoftext|>


<commit_before>// ==UserScript==
// @id             iitc-plugin-offle
// @name           IITC plugin: offle
// @category       Misc
// @version        0.3.4
// @namespace      https://github.com/vrabcak/iitc-offle
// @description    Offle
// @include        https://www.ingress.com/intel*
// @include        http://www.ingress.com/intel*
// @match          https://www.ingress.com/intel*
// @match          http://www.ingress.com/intel*
// @grant          none
// ==/UserScript==

function wrapper(plugin_info) {
    // ensure plugin framework is there, even if iitc is not yet loaded
    if (typeof window.plugin !== 'function') {
        window.plugin = function () {};
    }

    // PLUGIN START ////////////////////////////////////////////////////////

    // use own namespace for plugin
    window.plugin.offle = function () {};
    var offle = window.plugin.offle;
    offle.portalDb = {};
    offle.lastAddedDb = {};
    offle.symbol = '•';
    offle.symbolWithMission = '◉';
    offle.maxVisibleCount = 2000;

    // Use portal add event to save it to db
    offle.portalAdded = function (data) {
        offle.addPortal(
            data.portal.options.guid,
            data.portal.options.data.title,
            data.portal.getLatLng(),
            data.portal.options.data.mission
        );
    };

    // Always update portal data if displayed in sidebar (to handle e.g. moved portals)
    offle.portalDetailsUpdated = function (data) {
        var guid = data.portal.options.guid,
            name = data.portal.options.data.title;
        if (name) { //update data only with portals with full details
            offle.portalDb[guid] = data.portal.getLatLng();
            offle.portalDb[guid].name = data.portal.options.data.title;
            offle.portalDb[guid].mission = data.portal.options.data.mission;
            offle.renderVisiblePortals();
            localStorage.setItem('portalDb', JSON.stringify(offle.portalDb));
        }
    };

    offle.addPortal = function (guid, name, latLng, mission) {

        var notInDb = guid && !(guid in offle.portalDb);
        var newName = name && offle.portalDb[guid] && !offle.portalDb[guid].name;

        //console.log("AddPortal ", guid," ",name, "::", notInDb, " ", newName);

        if (notInDb || newName) {

            //add to last added list only new portals or update already displayed guid with name
            if (notInDb || (newName && (guid in offle.lastAddedDb))) {
                offle.lastAddedDb[guid] = {
                    name: name || guid,
                    latLng: latLng,
                    unique: false
                };

                if (!(window.plugin.uniques && (guid in window.plugin.uniques.uniques))) {
                offle.lastAddedDb[guid].unique = true;
                }
            }

            offle.portalDb[guid] = latLng;
            offle.portalDb[guid].name = name;
            offle.portalDb[guid].mission = mission;
            offle.dirtyDb = true; //mark Db dirty to by stored on mapDataRefreshEnd
            offle.renderPortal(guid);
            offle.updatePortalCounter();
            offle.updateLACounter();
            offle.updateLAList();
        }
    };

    offle.renderPortal = function (guid) {
        var portalMarker, uniqueInfo,
            iconCSSClass = 'offle-marker';

        if (window.plugin.uniques) {
            uniqueInfo = window.plugin.uniques.uniques[guid];
        }

        if (uniqueInfo) {
            if (uniqueInfo.visited) {
                iconCSSClass += ' offle-marker-visited-color';
            }
            if (uniqueInfo.captured) {
                iconCSSClass += ' offle-marker-captured-color';
            }
        }

        portalMarker = L.marker(offle.portalDb[guid], {
            icon: L.divIcon({
                className: iconCSSClass,
                iconAnchor: [15, 23],
                iconSize: [30, 30],
                html: offle.portalDb[guid].mission ? offle.symbolWithMission : offle.symbol
            }),
            name: offle.portalDb[guid].name,
            title: offle.portalDb[guid].name || ''
        });

        portalMarker.on('click', function () {
            window.renderPortalDetails(guid);
        });

        portalMarker.addTo(offle.portalLayerGroup);

    };

    offle.clearLayer = function () {
        offle.portalLayerGroup.clearLayers();
    };

    offle.mapDataRefreshEnd = function () {
        if (offle.dirtyDb) {
            //console.log("Storing new portals to localStorage");
            localStorage.setItem('portalDb', JSON.stringify(offle.portalDb));
        }
        offle.dirtyDb = false;
    };

    offle.setupLayer = function () {
        offle.portalLayerGroup = new L.LayerGroup();
        window.addLayerGroup('offlePortals', offle.portalLayerGroup, false);
    };

    offle.setupCSS = function () {
        $("