text: function FreeboardModel(datasourcePlugins, widgetPlugins, freeboardUI) {
      var self = this;
  
      var SERIALIZATION_VERSION = 1;
  
      this.version = 0;
      this.isEditing = ko.observable(false);
      this.allow_edit = ko.observable(false);
      this.allow_edit.subscribe(function(newValue) {
          if (newValue) {
              $("#main-header").show();
          } else {
              $("#main-header").hide();
          }
      });
  
      this.header_image = ko.observable();
      this.plugins = ko.observableArray();
      this.datasources = ko.observableArray();
      this.panes = ko.observableArray();
      this.datasourceData = {};
      this.processDatasourceUpdate = function(datasourceModel, newData) {
          var datasourceName = datasourceModel.name();
  
          self.datasourceData[datasourceName] = newData;
  
          _.each(self.panes(), function(pane) {
              _.each(pane.widgets(), function(widget) {
                  widget.processDatasourceUpdate(datasourceName);
              });
          });
      }
  
      this._datasourceTypes = ko.observable();
      this.datasourceTypes = ko.computed({
          read: function() {
              self._datasourceTypes();
  
              var returnTypes = [];
  
              _.each(datasourcePlugins, function(datasourcePluginType) {
                  var typeName = datasourcePluginType.type_name;
                  var displayName = typeName;
  
                  if (!_.isUndefined(datasourcePluginType.display_name)) {
                      displayName = datasourcePluginType.display_name;
                  }
  
                  returnTypes.push({
                      name: typeName,
                      display_name: displayName
                  });
              });
  
              return returnTypes;
          }
      });
  
      this._widgetTypes = ko.observable();
      this.widgetTypes = ko.computed({
          read: function() {
              self._widgetTypes();
  
              var returnTypes = [];
  
              _.each(widgetPlugins, function(widgetPluginType) {
                  var typeName = widgetPluginType.type_name;
                  var displayName = typeName;
  
                  if (!_.isUndefined(widgetPluginType.display_name)) {
                      displayName = widgetPluginType.display_name;
                  }
  
                  returnTypes.push({
                      name: typeName,
                      display_name: displayName
                  });
              });
  
              return returnTypes;
          }
      });
  
      this.addPluginSource = function(pluginSource) {
          if (pluginSource && self.plugins.indexOf(pluginSource) == -1) {
              self.plugins.push(pluginSource);
          }
      }
  
      this.serialize = function() {
          var panes = [];
  
          _.each(self.panes(), function(pane) {
              panes.push(pane.serialize());
          });
  
          var datasources = [];
  
          _.each(self.datasources(), function(datasource) {
              datasources.push(datasource.serialize());
          });
  
          return {
              version: SERIALIZATION_VERSION,
              header_image: self.header_image(),
              allow_edit: self.allow_edit(),
              plugins: self.plugins(),
              panes: panes,
              datasources: datasources,
              columns: freeboardUI.getUserColumns()
          };
      }
  
      this.deserialize = function(object, finishedCallback) {
          self.clearDashboard();
  
          function finishLoad() {
              freeboardUI.setUserColumns(object.columns);
  
              if (!_.isUndefined(object.allow_edit)) {
                  self.allow_edit(object.allow_edit);
              } else {
                  self.allow_edit(true);
              }
              self.version = object.version || 0;
              self.header_image(object.header_image);
  
              _.each(object.datasources, function(datasourceConfig) {
                  var datasource = new DatasourceModel(self, datasourcePlugins);
                  datasource.deserialize(datasourceConfig);
                  self.addDatasource(datasource);
              });
  
              var sortedPanes = _.sortBy(object.panes, function(pane) {
                  return freeboardUI.getPositionForScreenSize(pane).row;
              });
  
              _.each(sortedPanes, function(paneConfig) {
                  var pane = new PaneModel(self, widgetPlugins);
                  pane.deserialize(paneConfig);
                  self.panes.push(pane);
              });
  
              if (self.allow_edit() && self.panes().length == 0) {
                  self.setEditing(true);
              }
  
              if (_.isFunction(finishedCallback)) {
                  finishedCallback();
              }
  
              freeboardUI.processResize(true);
          }
  
          // This could have been self.plugins(object.plugins), but for some weird reason head.js was causing a function to be added to the list of plugins.
          _.each(object.plugins, function(plugin) {
              self.addPluginSource(plugin);
          });
  
          // Load any plugins referenced in this definition
          if (_.isArray(object.plugins) && object.plugins.length > 0) {
              head.js(object.plugins, function() {
                  finishLoad();
              });
          } else {
              finishLoad();
          }
      }
  
      this.clearDashboard = function() {
          freeboardUI.removeAllPanes();
  
          _.each(self.datasources(), function(datasource) {
              datasource.dispose();
          });
  
          _.each(self.panes(), function(pane) {
              pane.dispose();
          });
  
          self.plugins.removeAll();
          self.datasources.removeAll();
          self.panes.removeAll();
      }
  
      this.loadDashboard = function(dashboardData, callback) {
          freeboardUI.showLoadingIndicator(true);
          self.deserialize(dashboardData, function() {
              freeboardUI.showLoadingIndicator(false);
  
              if (_.isFunction(callback)) {
                  callback();
              }
  
              freeboard.emit("dashboard_loaded");
          });
      }
  
      this.loadDashboardFromLocalFile = function() {
          // Check for the various File API support.
          if (window.File && window.FileReader && window.FileList && window.Blob) {
              var input = document.createElement('input');
              input.type = "file";
              $(input).on("change", function(event) {
                  var files = event.target.files;
  
                  if (files && files.length > 0) {
                      var file = files[0];
                      var reader = new FileReader();
  
                      reader.addEventListener("load", function(fileReaderEvent) {
  
                          var textFile = fileReaderEvent.target;
                          var jsonObject = JSON.parse(textFile.result);
  
  
                          self.loadDashboard(jsonObject);
                          self.setEditing(false);
                      });
  
                      reader.readAsText(file);
                  }
  
              });
              $(input).trigger("click");
          } else {
              alert('سیستم موفق به بارگزاری فایل در براوزر نشد.');
          }
      }
  
      this.saveDashboardClicked = function() {
          var target = $(event.currentTarget);
          var siblingsShown = target.data('siblings-shown') || false;
          if (!siblingsShown) {
              $(event.currentTarget).siblings('label').fadeIn('slow');
          } else {
              $(event.currentTarget).siblings('label').fadeOut('slow');
          }
          target.data('siblings-shown', !siblingsShown);
      }
  
      this.saveDashboard = function(_thisref, event) {
          var pretty = $(event.currentTarget).data('pretty');
          var contentType = 'application/octet-stream';
          var a = document.createElement('a');
          if (pretty) {
              var blob = new Blob([JSON.stringify(self.serialize(), null, '\t')], { 'type': contentType });
          } else {
              var blob = new Blob([JSON.stringify(self.serialize())], { 'type': contentType });
          }
          document.body.appendChild(a);
          a.href = window.URL.createObjectURL(blob);
          a.download = "dashboard.json";
          a.target = "_self";
          a.click();
      }
  
      this.addDatasource = function(datasource) {
          self.datasources.push(datasource);
      }
  
      this.deleteDatasource = function(datasource) {
          delete self.datasourceData[datasource.name()];
          datasource.dispose();
          self.datasources.remove(datasource);
      }
  
      this.createPane = function() {
          var newPane = new PaneModel(self, widgetPlugins);
          self.addPane(newPane);
      }
  
      this.addGridColumnLeft = function() {
          freeboardUI.addGridColumnLeft();
      }
  
      this.addGridColumnRight = function() {
          freeboardUI.addGridColumnRight();
      }
  
      this.subGridColumnLeft = function() {
          freeboardUI.subGridColumnLeft();
      }
  
      this.subGridColumnRight = function() {
          freeboardUI.subGridColumnRight();
      }
  
      this.addPane = function(pane) {
          self.panes.push(pane);
      }
  
      this.deletePane = function(pane) {
          pane.dispose();
          self.panes.remove(pane);
      }
  
      this.deleteWidget = function(widget) {
          ko.utils.arrayForEach(self.panes(), function(pane) {
              pane.widgets.remove(widget);
          });
  
          widget.dispose();
      }
  
      this.setEditing = function(editing, animate) {
          // Don't allow editing if it's not allowed
          if (!self.allow_edit() && editing) {
              return;
          }
  
          self.isEditing(editing);
  
          if (_.isUndefined(animate)) {
              animate = true;
          }
  
          var animateLength = (animate) ? 250 : 0;
          var barHeight = $("#admin-bar").outerHeight();
  
          if (!editing) {
              $("#toggle-header-icon").addClass("icon-wrench").removeClass("icon-chevron-up");
              $(".gridster .gs_w").css({ cursor: "default" });
              $("#main-header").animate({ "top": "-" + barHeight + "px" }, animateLength);
              $("#board-content").animate({ "top": "20" }, animateLength);
              $("#main-header").data().shown = false;
              $(".sub-section").unbind();
              freeboardUI.disableGrid();
          } else {
              $("#toggle-header-icon").addClass("icon-chevron-up").removeClass("icon-wrench");
              $(".gridster .gs_w").css({ cursor: "pointer" });
              $("#main-header").animate({ "top": "0px" }, animateLength);
              $("#board-content").animate({ "top": (barHeight + 20) + "px" }, animateLength);
              $("#main-header").data().shown = true;
              freeboardUI.attachWidgetEditIcons($(".sub-section"));
              freeboardUI.enableGrid();
          }
  
          freeboardUI.showPaneEditIcons(editing, animate);
      }
  
      this.toggleEditing = function() {
          var editing = !self.isEditing();
          self.setEditing(editing);
      }
  }