<commit_before>/**
 * @file
 * deal with browser history, move back and forward, etc...
 * @author  Karl Krissian
 * @version 0.1
 */

// using strict mode: better compatibility
"use strict";

// ipol application namespace
var ipol = ipol || {};

/**
 * history
 * @namespace
 */
ipol.history = ipol.history || {};

//------------------------------------------------------------------------------
/**
 * Set page contents based on its state (obtained from browser history)
 * @constructor
 * @param {object} page_state
 */
ipol.history.SetPageState = function( page_state) {

    //--------------------------------------------------------------------------
    /**
     * Set the demo id: changes the selection and the page contents accordingly
     * @param demo_id     {number}
     * @param onpage_func {callback}
     * @function SetPageDemo
     * @memberOf ipol.history.SetPageState~
     */
    function SetPageDemo(demo_id, onpage_func) {
        if (demo_id===undefined) {
            return;
        }
        var demo_list = $("#demo-select").data("demo_list");
        var demo_position = -1;
        for(var i=0;i<demo_list.length;i++) {
            if (demo_list[i].editorsdemoid==demo_id) {
                demo_position = i;
                break;
            }
        }

        if ((demo_position!=-1)&&(demo_position!=$("#demo_selection").val())) {
            console.info("!!! demo id has changed !!! trigger change ",
                        demo_list[$("#demo_selection").val()].editorsdemoid, ",",
                        "-->", demo_id);
            $("#demo_selection").val(demo_position);
            // don't trigger change since it will push a new history state,
            // instead, execute the change as if the url was loaded
            // which means we draw parameters and results too
            ipol.setDemoPage(demo_list[demo_position].editorsdemoid,
                        demo_list[demo_position].id,
                        ipol.demo_origin.browser_history,
                        onpage_func
                        );
        } else {
            onpage_func();
        }
    }

    //--------------------------------------------------------------------------
    /**
     * Set the demo inputs
     * @param blobset       {object}  blob set to display (can be undefined)
     * @param upload_state  {object}  upload state (can be undefined)
     * @param ddl_json      {object}  DDL object
     * @param params        {object}  algorithm parameters
     * @param crop_checked  {boolean} if crop is checked
     *                                (can be undefined)
     * @function SetInputsState
     * @memberOf ipol.history.SetPageState~
     */
    function SetInputsState(blobset, upload_state, ddl_json, params,
                            crop_checked, onload_callback) {
        var di = $("#DrawInputs").data("draw_inputs");
        var crop_area = {
            x:      params.x0,
            y:      params.y0,
            width:  params.x1-params.x0,
            height: params.y1-params.y0
        };

        // recreate only if it has changed:
        if ((!di)||(!ipol.utils.objectEquals(ddl_json,di.ddl_json))) {
            if (di) { console.info("2", ddl_json, " != ",di.ddl_json); }
            di = new ipol.DrawInputs(ddl_json);
            // just in case, be sure nothing is on upload page
            di.unsetUploadPageState();
        }
        if (blobset) {
            console.info("3");
            if (!ipol.utils.objectEquals(blobset,di.getBlobSet())) {
                console.info("4 ",blobset,' != ', di.getBlobSet());
                di.setBlobSet(blobset);
                di.setInputOrigin("blobset");
                di.unsetUploadPageState();
                di.createHTML();
                $("#id_cropinput").prop('checked',crop_checked);
                if (crop_checked) {
                    di.onCropBuilt( function() {
                        console.info("OnCropBuilt callback");
                        di.onCropBuilt( undefined);
                        // does not work yet
                        console.info("crop_area is ", crop_area);
                        di.setCrop(crop_area);
                        onload_callback();
                    });
                } else {
                    di.onLoadImages( function() {
                        console.info("**** OnLoadImages callback");
                        onload_callback();
                    });
                }
                di.loadDataFromBlobSet();
            } else {
                console.info("5");
                // blobset is already loaded, set crop
                if (crop_checked!=$("#id_cropinput").prop('checked')) {
                    if (crop_checked) {
                        // change from crop disabled to crop unabled, start crop
                        di.onCropBuilt( function() {
                            di.setCrop(crop_area);
                            di.onCropBuilt(undefined);
                            onload_callback();
                        });
                    } else {
                        onload_callback();
                    }
                    $("#id_cropinput").prop('checked',crop_checked);
                    console.info('trigger croinput change');
                    $("#id_cropinput").trigger('change');
                } else {
                    if (crop_checked) {
                        // does not work yet
                        console.info("crop_area is ", crop_area);
                        di.setCrop(crop_area);
                    }
                    onload_callback();
                }
            }
        }
        if (upload_state) {
            console.info("10");
            var upload_res = di.setUploadPageState(upload_state);
            if (upload_res!=1) {
                di.unsetBlobSet();
                di.setInputOrigin("localfiles");
                if (upload_res==0) {
                    di.createHTML();
                    $("#id_cropinput").prop('checked',crop_checked);
                    if (crop_checked) {
                        di.onCropBuilt( function() {
                            console.info("OnCropBuilt callback");
                            di.onCropBuilt( undefined);
                            di.setCrop(crop_area);
                            onload_callback();
                        });
                    } else {
                        di.onLoadImages( function() {
                            onload_callback();
                        });
                        onload_callback(); // [Miguel] Calling this
                        // function here is a workaround patch to make
                        // the results show.
                        // However, this ipol.history.js file is
                        // supposed to deal with the history, not with
                        // images! Moreover, IPOL does not work only with
                        // images and thus any type which is not an image
                        // will fail here (!!!)
                    }
                    di.loadDataFromLocalFiles();
                } else {
                    // data alread loaded, setting crop
                    if (crop_checked!=$("#id_cropinput").prop('checked')) {
                        if (crop_checked) {
                            // change from crop disabled to crop unabled, start crop
                            di.onCropBuilt( function() {
                                di.setCrop(crop_area);
                                di.onCropBuilt(undefined);
                                onload_callback();
                            });
                        } else {
                            onload_callback();
                        }
                        $("#id_cropinput").prop('checked',crop_checked);
                        console.info('trigger croinput change');
                        $("#id_cropinput").trigger('change');
                    } else {
                        if (crop_checked) {
                            console.info("crop_area is ", crop_area);
                            di.setCrop(crop_area);
                            onload_callback();
                        }
                    }
                }
            } else {
                $("#DrawInputs").empty();
                $("#DrawInputs").removeData();
            }
        }
    }

    //--------------------------------------------------------------------------
    /**
     * Set the feature drawing state
     * @function SetDrawFeatureState
     * @memberOf ipol.history.SetPageState~
     */
    function SetDrawFeatureState(feature_state) {
        if (feature_state) {
            var di = $("#DrawInputs").data("draw_inputs");
            di.getDrawFeature().setState(feature_state);
        }
    }

    //--------------------------------------------------------------------------
    /**
     * Set the parameters values
     * @param params        {object}  algorithm parameters
     * @function SetParamsState
     * @memberOf ipol.history.SetPageState~
     */
    function SetParamsState(params) {
        console.info("**** SetParamsState");
        // update parameters
        ipol.DrawParams.staticSetParamValues(params);
    }

    //--------------------------------------------------------------------------
    /**
     * Displays the results
     * @param res         {object}  result object
     * @param ddl_results {object}  results section of the DDL
     * @param scrolltop   {number}  scrolling position
     * @function SetResultsState
     * @memberOf ipol.history.SetPageState~
     */
    function SetResultsState(res,ddl_results,scrolltop) {
        // draw results
        // trick to avoid flickering, set big height to
        // keep the scrolling position
        $("#ResultsDisplay").parent().css("height",$(window).height()+"px")

        var dr = new ipol.DrawResults( res, ddl_results );

        // sets the scroll position when all images are loaded
        dr.setOnLoadAllCallback( function() {
            // reset result display height to empty so it is automatic
            $("#ResultsDisplay").parent().css("height","")
            $(window).scrollTop(scrolltop);
            console.info("onloadall_callback scrolltop=",scrolltop);
            // disable it
            console.info("dr=",dr);

        });
        dr.create();
    }

    //--------------------------------------------------------------------------
    // calling SetPageDemo with a callback
    SetPageDemo(page_state.demo_id,
        function() {
            History.log('statechange:', page_state);
            switch (page_state.state) {
                // state 2: after run execution
                // set all the page information
                case 2:

                    SetInputsState(
                        page_state.blobset,
                        page_state.upload_state,
                        page_state.ddl_json,
                        page_state.res.params,
                        page_state.crop_checked,
                        function() {

                            // Create RunDemo instance
                            var di = $("#DrawInputs").data("draw_inputs");
                            var run = new ipol.RunDemo( page_state.ddl_json,
                                                        di.getInputOrigin(),
                                                        di.getCropInfo(),
                                                        di.getBlobSet(),
                                                        di.getDrawFeature()
                                                        );
                            run.setRunEvent();

                            // update parameters once the inputs
                            // are loaded
                            SetDrawFeatureState(  page_state.feature_state);
                            SetParamsState(       page_state.res.params);
                            SetResultsState(      page_state.res,
                                                  page_state.ddl_json.results,
                                                  page_state.scrolltop);
                        }
                    );

                    break;
                // state 1: after demo selection
                case 1:
                default:
                    // empty draw inputs since we don't redraw them for the moment
                    $("#DrawInputs").empty();
                    $("#DrawInputs").removeData();
                    // empty result area
                    $("#ResultsDisplay").empty();
            }
        }
    );
}
<commit_msg>[clientApp/ipol.rundemo.js] Bug: reading undefined di in SetPageDemo in the noinputs case<commit_after>/**
 * @file
 * deal with browser history, move back and forward, etc...
 * @author  Karl Krissian
 * @version 0.1
 */

// using strict mode: better compatibility
"use strict";

// ipol application namespace
var ipol = ipol || {};

/**
 * history
 * @namespace
 */
ipol.history = ipol.history || {};

//------------------------------------------------------------------------------
/**
 * Set page contents based on its state (obtained from browser history)
 * @constructor
 * @param {object} page_state
 */
ipol.history.SetPageState = function( page_state) {

    //--------------------------------------------------------------------------
    /**
     * Set the demo id: changes the selection and the page contents accordingly
     * @param demo_id     {number}
     * @param onpage_func {callback}
     * @function SetPageDemo
     * @memberOf ipol.history.SetPageState~
     */
    function SetPageDemo(demo_id, onpage_func) {
        if (demo_id===undefined) {
            return;
        }
        var demo_list = $("#demo-select").data("demo_list");
        var demo_position = -1;
        for(var i=0;i<demo_list.length;i++) {
            if (demo_list[i].editorsdemoid==demo_id) {
                demo_position = i;
                break;
            }
        }

        if ((demo_position!=-1)&&(demo_position!=$("#demo_selection").val())) {
            console.info("!!! demo id has changed !!! trigger change ",
                        demo_list[$("#demo_selection").val()].editorsdemoid, ",",
                        "-->", demo_id);
            $("#demo_selection").val(demo_position);
            // don't trigger change since it will push a new history state,
            // instead, execute the change as if the url was loaded
            // which means we draw parameters and results too
            ipol.setDemoPage(demo_list[demo_position].editorsdemoid,
                        demo_list[demo_position].id,
                        ipol.demo_origin.browser_history,
                        onpage_func
                        );
        } else {
            onpage_func();
        }
    }

    //--------------------------------------------------------------------------
    /**
     * Set the demo inputs
     * @param blobset       {object}  blob set to display (can be undefined)
     * @param upload_state  {object}  upload state (can be undefined)
     * @param ddl_json      {object}  DDL object
     * @param params        {object}  algorithm parameters
     * @param crop_checked  {boolean} if crop is checked
     *                                (can be undefined)
     * @function SetInputsState
     * @memberOf ipol.history.SetPageState~
     */
    function SetInputsState(blobset, upload_state, ddl_json, params,
                            crop_checked, onload_callback) {
        var di = $("#DrawInputs").data("draw_inputs");
        var crop_area = {
            x:      params.x0,
            y:      params.y0,
            width:  params.x1-params.x0,
            height: params.y1-params.y0
        };

        // recreate only if it has changed:
        if ((!di)||(!ipol.utils.objectEquals(ddl_json,di.ddl_json))) {
            if (di) { console.info("2", ddl_json, " != ",di.ddl_json); }
            di = new ipol.DrawInputs(ddl_json);
            // just in case, be sure nothing is on upload page
            di.unsetUploadPageState();
        }
        if (blobset) {
            console.info("3");
            if (!ipol.utils.objectEquals(blobset,di.getBlobSet())) {
                console.info("4 ",blobset,' != ', di.getBlobSet());
                di.setBlobSet(blobset);
                di.setInputOrigin("blobset");
                di.unsetUploadPageState();
                di.createHTML();
                $("#id_cropinput").prop('checked',crop_checked);
                if (crop_checked) {
                    di.onCropBuilt( function() {
                        console.info("OnCropBuilt callback");
                        di.onCropBuilt( undefined);
                        // does not work yet
                        console.info("crop_area is ", crop_area);
                        di.setCrop(crop_area);
                        onload_callback();
                    });
                } else {
                    di.onLoadImages( function() {
                        console.info("**** OnLoadImages callback");
                        onload_callback();
                    });
                }
                di.loadDataFromBlobSet();
            } else {
                console.info("5");
                // blobset is already loaded, set crop
                if (crop_checked!=$("#id_cropinput").prop('checked')) {
                    if (crop_checked) {
                        // change from crop disabled to crop unabled, start crop
                        di.onCropBuilt( function() {
                            di.setCrop(crop_area);
                            di.onCropBuilt(undefined);
                            onload_callback();
                        });
                    } else {
                        onload_callback();
                    }
                    $("#id_cropinput").prop('checked',crop_checked);
                    console.info('trigger croinput change');
                    $("#id_cropinput").trigger('change');
                } else {
                    if (crop_checked) {
                        // does not work yet
                        console.info("crop_area is ", crop_area);
                        di.setCrop(crop_area);
                    }
                    onload_callback();
                }
            }
        }
        if (upload_state) {
            console.info("10");
            var upload_res = di.setUploadPageState(upload_state);
            if (upload_res!=1) {
                di.unsetBlobSet();
                di.setInputOrigin("localfiles");
                if (upload_res==0) {
                    di.createHTML();
                    $("#id_cropinput").prop('checked',crop_checked);
                    if (crop_checked) {
                        di.onCropBuilt( function() {
                            console.info("OnCropBuilt callback");
                            di.onCropBuilt( undefined);
                            di.setCrop(crop_area);
                            onload_callback();
                        });
                    } else {
                        di.onLoadImages( function() {
                            onload_callback();
                        });
                        onload_callback(); // [Miguel] Calling this
                        // function here is a workaround patch to make
                        // the results show.
                        // However, this ipol.history.js file is
                        // supposed to deal with the history, not with
                        // images! Moreover, IPOL does not work only with
                        // images and thus any type which is not an image
                        // will fail here (!!!)
                    }
                    di.loadDataFromLocalFiles();
                } else {
                    // data alread loaded, setting crop
                    if (crop_checked!=$("#id_cropinput").prop('checked')) {
                        if (crop_checked) {
                            // change from crop disabled to crop unabled, start crop
                            di.onCropBuilt( function() {
                                di.setCrop(crop_area);
                                di.onCropBuilt(undefined);
                                onload_callback();
                            });
                        } else {
                            onload_callback();
                        }
                        $("#id_cropinput").prop('checked',crop_checked);
                        console.info('trigger croinput change');
                        $("#id_cropinput").trigger('change');
                    } else {
                        if (crop_checked) {
                            console.info("crop_area is ", crop_area);
                            di.setCrop(crop_area);
                            onload_callback();
                        }
                    }
                }
            } else {
                $("#DrawInputs").empty();
                $("#DrawInputs").removeData();
            }
        }
    }

    //--------------------------------------------------------------------------
    /**
     * Set the feature drawing state
     * @function SetDrawFeatureState
     * @memberOf ipol.history.SetPageState~
     */
    function SetDrawFeatureState(feature_state) {
        if (feature_state) {
            var di = $("#DrawInputs").data("draw_inputs");
            di.getDrawFeature().setState(feature_state);
        }
    }

    //--------------------------------------------------------------------------
    /**
     * Set the parameters values
     * @param params        {object}  algorithm parameters
     * @function SetParamsState
     * @memberOf ipol.history.SetPageState~
     */
    function SetParamsState(params) {
        console.info("**** SetParamsState");
        // update parameters
        ipol.DrawParams.staticSetParamValues(params);
    }

    //--------------------------------------------------------------------------
    /**
     * Displays the results
     * @param res         {object}  result object
     * @param ddl_results {object}  results section of the DDL
     * @param scrolltop   {number}  scrolling position
     * @function SetResultsState
     * @memberOf ipol.history.SetPageState~
     */
    function SetResultsState(res,ddl_results,scrolltop) {
        // draw results
        // trick to avoid flickering, set big height to
        // keep the scrolling position
        $("#ResultsDisplay").parent().css("height",$(window).height()+"px")

        var dr = new ipol.DrawResults( res, ddl_results );

        // sets the scroll position when all images are loaded
        dr.setOnLoadAllCallback( function() {
            // reset result display height to empty so it is automatic
            $("#ResultsDisplay").parent().css("height","")
            $(window).scrollTop(scrolltop);
            console.info("onloadall_callback scrolltop=",scrolltop);
            // disable it
            console.info("dr=",dr);

        });
        dr.create();
    }

    //--------------------------------------------------------------------------
    // calling SetPageDemo with a callback
    SetPageDemo(page_state.demo_id,
        function() {
            History.log('statechange:', page_state);
            switch (page_state.state) {
                // state 2: after run execution
                // set all the page information
                case 2:

                    SetInputsState(
                        page_state.blobset,
                        page_state.upload_state,
                        page_state.ddl_json,
                        page_state.res.params,
                        page_state.crop_checked,
                        function() {

                            // Create RunDemo instance
                            var di = $("#DrawInputs").data("draw_inputs");
                            if (di) {
                                var run = new ipol.RunDemo( page_state.ddl_json,
                                                            di.getInputOrigin(),
                                                            di.getCropInfo(),
                                                            di.getBlobSet(),
                                                            di.getDrawFeature()
                                                            );
                            }
                            else {
                                var run = new ipol.RunDemo( page_state.ddl_json,
                                                            "noinputs",
                                                            null,
                                                            null,
                                                            null
                                                            );
                            }

                            run.setRunEvent();

                            // update parameters once the inputs
                            // are loaded
                            SetDrawFeatureState(  page_state.feature_state);
                            SetParamsState(       page_state.res.params);
                            SetResultsState(      page_state.res,
                                                  page_state.ddl_json.results,
                                                  page_state.scrolltop);
                        }
                    );

                    break;
                // state 1: after demo selection
                case 1:
                default:
                    // empty draw inputs since we don't redraw them for the moment
                    $("#DrawInputs").empty();
                    $("#DrawInputs").removeData();
                    // empty result area
                    $("#ResultsDisplay").empty();
            }
        }
    );
}
<|endoftext|>


<commit_before>// plone integration for textext.
//
// Author: Rok Garbas
// Contact: rok@garbas.si
// Version: 1.0
// Depends:
//    ++resource++plone.app.jquery.js
//    ++resource++plone.app.widgets/textext.js
//
// Description:
//
// License:
//
// Copyright (C) 2010 Plone Foundation
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

define([
  'jquery',
  'underscore',
  'mockup-patterns-base',
  'mockup-patterns-select2',
  'mockup-patterns-queryhelper'
], function($, _, Base, Select2, QueryHelper) {
  "use strict";

  var RelatedItems = Base.extend({
    name: "relateditems",
    browsing: false,
    currentPath: null,
    defaults: {
      ajaxvocabulary: null, // must be set to work
      multiple: true,
      tokenSeparators: [",", " "],
      separator: ",",
      orderable: true,
      cache: true,
      closeOnSelect: false,
      basePath: '/',
      browseText: 'Browse',
      searchText: 'Search',
      homeText: 'home',
      folderTypes: ['Folder'],
      attributes: ['UID','Title', 'Type', 'path'],
      dropdownCssClass: 'pat-relateditems-dropdown',
      maximumSelectionSize: -1,
      resultTemplate: '' +
        '<div class="pat-relateditems-result pat-relateditems-type-<%= Type %>">' +
        '  <a href="#" class="pat-relateditems-result-select <% if (selected) { %>pat-active<% } %>">' +
        '    <%= Title %>' +
        '    <%= path %>' +
        '  ' +
        '  ' +
        '  <% if (folderish) { %>' +
        '     <a class="pat-relateditems-result-browse" href="#" data-path="<%= path %>">' +
        '   <% } %>' +
        ' ' +
        '
',
      resultTemplateSelector: null,
      selectionTemplate: '' +
        '<span class="pat-relateditems-item pat-relateditems-type-<%= Type %>">' +
        ' <%= Title %>' +
        ' <%= path %>' +
        '',
      selectionTemplateSelector: null,
      tabsTemplate: '' +
        '
' +
        ' <%= searchText %>' +
        ' <%= browseText %>' +
        '
',
      tabsTemplateSelector: null,
      breadCrumbsTemplate: '' +
        '<%= items %>',
      breadCrumbsTemplateSelector: null,
      breadCrumbTemplate: '' +
        '/<a href="<%= path %>"><%= text %>',
      breadCrumbTemplateSelector: null,
      escapeMarkup: function(text) {
        return text;
      }
    },
    applyTemplate: function(tpl, item) {
      var self = this;
      var template;
      if (self.options[tpl+'TemplateSelector']) {
        template = $(self.options[tpl+'TemplateSelector']).html();
        if (!template) {
          template = self.options[tpl+'Template'];
        }
      } else {
        template = self.options[tpl+'Template'];
      }
      return _.template(template, item);
    },
    activateBrowsing: function(){
      var self = this;
      self.browsing = true;
      self.setBreadCrumbs();
      self.setTabs();
    },
    deactivateBrowsing: function(){
      var self = this;
      self.browsing = false;
      self.setBreadCrumbs();
      self.setTabs();
    },
    browseTo: function(path) {
      var self = this;
      self.trigger('before-browse');
      self.currentPath = path;
      self.activateBrowsing();
      self.$el.select2('close');
      self.$el.select2('open');
      self.trigger('after-browse');
    },
    setTabs: function() {
      var self = this;
      if (self.browsing) {
        self.$browseBtn.addClass('pat-active');
        self.$searchBtn.removeClass('pat-active');
      } else {
        self.$browseBtn.removeClass('pat-active');
        self.$searchBtn.addClass('pat-active');
      }
    },
    setBreadCrumbs: function() {
      var self = this;
      var path = self.currentPath ? self.currentPath : self.options.basePath;
      if (self.browsing) {
        var html;
        if (path === '/') {
          html = self.applyTemplate('breadCrumbs', {items:''});
        } else {
          var paths = path.split('/');
          var itemPath = '';
          var itemsHtml = '';
          _.each(paths, function(node) {
            if (node !== '') {
              var item = {};
              itemPath = itemPath + '/' + node;
              item.text = node;
              item.path = itemPath;
              itemsHtml = itemsHtml + self.applyTemplate('breadCrumb', item);
            }
          });
          html = self.applyTemplate('breadCrumbs', {items:itemsHtml});
        }
        var $crumbs = $(html);
        $('a', $crumbs).on('click', function(event) {
          self.browseTo($(this).attr('href'));
          return false;
        });
        self.$browsePath.html($crumbs);
      } else {
        self.$browsePath.html('');
      }
    },
    selectItem: function(item) {
      var self = this;
      self.trigger('selecting');
      var data = self.$el.select2("data");
      data.push(item);
      self.$el.select2("data", data);
      item.selected = true;
      self.trigger('selected');
    },
    deselectItem: function(item) {
      var self = this;
      self.trigger('deselecting');
      var data = self.$el.select2("data");
      _.each(data, function(obj, i) {
        if (obj.id === item.id) {
          data.splice(i, 1);
        }
      });
      self.$el.select2("data", data);
      item.selected = false;
      self.trigger('deselected');
    },
    init: function() {
      var self = this;

      self.query = new QueryHelper(self.$el,
          $.extend(true, {}, self.options, {basePattern: self}));

      self.$el.wrap('
');
      self.$container = self.$el.parents('.pat-relateditems-container');
      self.$container.width(self.options.width);

      Select2.prototype.initializeValueMap.call(self);
      Select2.prototype.initializeTags.call(self);
      Select2.prototype.initializeOrdering.call(self);

      if(self.query.valid){
        self.options.ajax = self.query.selectAjax();
      } else {
        self.options.tags = [];
      }

      self.options.formatSelection = function(item) {
        return self.applyTemplate('selection', item);
      };

      self.options.formatResult = function(item) {
        if(!item.Type || _.indexOf(self.options.folderTypes, item.Type) === -1){
          item.folderish = false;
        }else{
          item.folderish = true;
        }

        if (item.selected === undefined) {
          var data = self.$el.select2("data");
          item.selected = false;
          _.each(data, function(obj) {
            if (obj.UID === item.UID) {
              item.selected = true;
            }
          });
        }

        var result = $(self.applyTemplate('result', item));

        $('.pat-relateditems-result-select', result).on('click', function(event) {
          event.preventDefault();
          if ($(this).is('.pat-active')) {
            $(this).removeClass('pat-active');
            self.deselectItem(item);
          } else {
            self.selectItem(item);
            $(this).addClass('pat-active');
            if(self.options.maximumSelectionSize > 0){
              var items = self.$select2.select2('data');
              if(items.length >= self.options.maximumSelectionSize){
                self.$select2.select2('close');
              }
            }
          }
        });

        $('.pat-relateditems-result-browse', result).on('click', function(event) {
          event.preventDefault();
          event.stopPropagation();
          var path = $(this).data('path');
          self.browseTo(path);
        });

        return $(result);
      };
      self.options.initSelection = function(element, callback) {
        var data = [];
        var value = $(element).val();
        if (value !== '') {
          var ids = value.split(self.options.separator);
          self.query.search(
            'UID', 'plone.app.querystring.operation.list.contains', ids,
            function(data){
              callback(data.results);
          }, false);
        }
      };

      self.options.id = function(item) {
        /* Trick select2 into not removing an item from the list if it has been added.
           Allows user to browse folders that have been selected. */
        return item.UID + '/' + Math.random() + '' + Math.random();
      };

      Select2.prototype.initializeSelect2.call(self);

      // Browsing functionality
      var browseOpts = {
        browseText: self.options.browseText,
        searchText: self.options.searchText
      };
      self.$browse = $(self.applyTemplate('tabs', browseOpts));
      self.$container.prepend(self.$browse);
      self.$browseBtn = $('.pat-relateditems-tabs-browse', self.$browse);
      self.$searchBtn = $('.pat-relateditems-tabs-search', self.$browse);
      self.$browsePath = $('');
      self.$browse.after(self.$browsePath);
      self.deactivateBrowsing();

      self.$browseBtn.click(function(e){
        self.activateBrowsing();
        self.$el.select2('close');
        self.$el.select2('open');
        return false;
      });

      self.$searchBtn.click(function(e){
        self.deactivateBrowsing();
        self.$el.select2('close');
        self.$el.select2('open');
        return false;
      });

      self.$el.on("select2-selecting", function(event) {
        event.preventDefault();
      });

    }
  });

  return RelatedItems;

});

<commit_msg>when unselecting items compare them by something defined<commit_after>// plone integration for textext.
//
// Author: Rok Garbas
// Contact: rok@garbas.si
// Version: 1.0
// Depends:
//    ++resource++plone.app.jquery.js
//    ++resource++plone.app.widgets/textext.js
//
// Description:
//
// License:
//
// Copyright (C) 2010 Plone Foundation
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

define([
  'jquery',
  'underscore',
  'mockup-patterns-base',
  'mockup-patterns-select2',
  'mockup-patterns-queryhelper'
], function($, _, Base, Select2, QueryHelper) {
  "use strict";

  var RelatedItems = Base.extend({
    name: "relateditems",
    browsing: false,
    currentPath: null,
    defaults: {
      ajaxvocabulary: null, // must be set to work
      multiple: true,
      tokenSeparators: [",", " "],
      separator: ",",
      orderable: true,
      cache: true,
      closeOnSelect: false,
      basePath: '/',
      browseText: 'Browse',
      searchText: 'Search',
      homeText: 'home',
      folderTypes: ['Folder'],
      attributes: ['UID','Title', 'Type', 'path'],
      dropdownCssClass: 'pat-relateditems-dropdown',
      maximumSelectionSize: -1,
      resultTemplate: '' +
        '<div class="pat-relateditems-result pat-relateditems-type-<%= Type %>">' +
        '  <a href="#" class="pat-relateditems-result-select <% if (selected) { %>pat-active<% } %>">' +
        '    <%= Title %>' +
        '    <%= path %>' +
        '  ' +
        '  ' +
        '  <% if (folderish) { %>' +
        '     <a class="pat-relateditems-result-browse" href="#" data-path="<%= path %>">' +
        '   <% } %>' +
        ' ' +
        '
',
      resultTemplateSelector: null,
      selectionTemplate: '' +
        '<span class="pat-relateditems-item pat-relateditems-type-<%= Type %>">' +
        ' <%= Title %>' +
        ' <%= path %>' +
        '',
      selectionTemplateSelector: null,
      tabsTemplate: '' +
        '
' +
        ' <%= searchText %>' +
        ' <%= browseText %>' +
        '
',
      tabsTemplateSelector: null,
      breadCrumbsTemplate: '' +
        '<%= items %>',
      breadCrumbsTemplateSelector: null,
      breadCrumbTemplate: '' +
        '/<a href="<%= path %>"><%= text %>',
      breadCrumbTemplateSelector: null,
      escapeMarkup: function(text) {
        return text;
      }
    },
    applyTemplate: function(tpl, item) {
      var self = this;
      var template;
      if (self.options[tpl+'TemplateSelector']) {
        template = $(self.options[tpl+'TemplateSelector']).html();
        if (!template) {
          template = self.options[tpl+'Template'];
        }
      } else {
        template = self.options[tpl+'Template'];
      }
      return _.template(template, item);
    },
    activateBrowsing: function(){
      var self = this;
      self.browsing = true;
      self.setBreadCrumbs();
      self.setTabs();
    },
    deactivateBrowsing: function(){
      var self = this;
      self.browsing = false;
      self.setBreadCrumbs();
      self.setTabs();
    },
    browseTo: function(path) {
      var self = this;
      self.trigger('before-browse');
      self.currentPath = path;
      self.activateBrowsing();
      self.$el.select2('close');
      self.$el.select2('open');
      self.trigger('after-browse');
    },
    setTabs: function() {
      var self = this;
      if (self.browsing) {
        self.$browseBtn.addClass('pat-active');
        self.$searchBtn.removeClass('pat-active');
      } else {
        self.$browseBtn.removeClass('pat-active');
        self.$searchBtn.addClass('pat-active');
      }
    },
    setBreadCrumbs: function() {
      var self = this;
      var path = self.currentPath ? self.currentPath : self.options.basePath;
      if (self.browsing) {
        var html;
        if (path === '/') {
          html = self.applyTemplate('breadCrumbs', {items:''});
        } else {
          var paths = path.split('/');
          var itemPath = '';
          var itemsHtml = '';
          _.each(paths, function(node) {
            if (node !== '') {
              var item = {};
              itemPath = itemPath + '/' + node;
              item.text = node;
              item.path = itemPath;
              itemsHtml = itemsHtml + self.applyTemplate('breadCrumb', item);
            }
          });
          html = self.applyTemplate('breadCrumbs', {items:itemsHtml});
        }
        var $crumbs = $(html);
        $('a', $crumbs).on('click', function(event) {
          self.browseTo($(this).attr('href'));
          return false;
        });
        self.$browsePath.html($crumbs);
      } else {
        self.$browsePath.html('');
      }
    },
    selectItem: function(item) {
      var self = this;
      self.trigger('selecting');
      var data = self.$el.select2("data");
      data.push(item);
      self.$el.select2("data", data);
      item.selected = true;
      self.trigger('selected');
    },
    deselectItem: function(item) {
      var self = this;
      self.trigger('deselecting');
      var data = self.$el.select2("data");
      _.each(data, function(obj, i) {
        if (obj.UID === item.UID) {
          data.splice(i, 1);
        }
      });
      self.$el.select2("data", data);
      item.selected = false;
      self.trigger('deselected');
    },
    init: function() {
      var self = this;

      self.query = new QueryHelper(self.$el,
          $.extend(true, {}, self.options, {basePattern: self}));

      self.$el.wrap('
');
      self.$container = self.$el.parents('.pat-relateditems-container');
      self.$container.width(self.options.width);

      Select2.prototype.initializeValueMap.call(self);
      Select2.prototype.initializeTags.call(self);
      Select2.prototype.initializeOrdering.call(self);

      if(self.query.valid){
        self.options.ajax = self.query.selectAjax();
      } else {
        self.options.tags = [];
      }

      self.options.formatSelection = function(item) {
        return self.applyTemplate('selection', item);
      };

      self.options.formatResult = function(item) {
        if(!item.Type || _.indexOf(self.options.folderTypes, item.Type) === -1){
          item.folderish = false;
        }else{
          item.folderish = true;
        }

        if (item.selected === undefined) {
          var data = self.$el.select2("data");
          item.selected = false;
          _.each(data, function(obj) {
            if (obj.UID === item.UID) {
              item.selected = true;
            }
          });
        }

        var result = $(self.applyTemplate('result', item));

        $('.pat-relateditems-result-select', result).on('click', function(event) {
          event.preventDefault();
          if ($(this).is('.pat-active')) {
            $(this).removeClass('pat-active');
            self.deselectItem(item);
          } else {
            self.selectItem(item);
            $(this).addClass('pat-active');
            if(self.options.maximumSelectionSize > 0){
              var items = self.$select2.select2('data');
              if(items.length >= self.options.maximumSelectionSize){
                self.$select2.select2('close');
              }
            }
          }
        });

        $('.pat-relateditems-result-browse', result).on('click', function(event) {
          event.preventDefault();
          event.stopPropagation();
          var path = $(this).data('path');
          self.browseTo(path);
        });

        return $(result);
      };
      self.options.initSelection = function(element, callback) {
        var data = [];
        var value = $(element).val();
        if (value !== '') {
          var ids = value.split(self.options.separator);
          self.query.search(
            'UID', 'plone.app.querystring.operation.list.contains', ids,
            function(data){
              callback(data.results);
          }, false);
        }
      };

      self.options.id = function(item) {
        /* Trick select2 into not removing an item from the list if it has been added.
           Allows user to browse folders that have been selected. */
        return item.UID + '/' + Math.random() + '' + Math.random();
      };

      Select2.prototype.initializeSelect2.call(self);

      // Browsing functionality
      var browseOpts = {
        browseText: self.options.browseText,
        searchText: self.options.searchText
      };
      self.$browse = $(self.applyTemplate('tabs', browseOpts));
      self.$container.prepend(self.$browse);
      self.$browseBtn = $('.pat-relateditems-tabs-browse', self.$browse);
      self.$searchBtn = $('.pat-relateditems-tabs-search', self.$browse);
      self.$browsePath = $('');
      self.$browse.after(self.$browsePath);
      self.deactivateBrowsing();

      self.$browseBtn.click(function(e){
        self.activateBrowsing();
        self.$el.select2('close');
        self.$el.select2('open');
        return false;
      });

      self.$searchBtn.click(function(e){
        self.deactivateBrowsing();
        self.$el.select2('close');
        self.$el.select2('open');
        return false;
      });

      self.$el.on("select2-selecting", function(event) {
        event.preventDefault();
      });

    }
  });

  return RelatedItems;

});

<|endoftext|>


<commit_before>import $ from 'jquery';

import TIMEOUT from './defaults/timeout';
import iconBs from './defaults/icon';
import iconFa from './defaults/iconFa';

let timer;
export default {
    ICONS: {
        BOOTSTRAP: iconBs,
        FONTAWESOME: iconFa
    },
    icon: iconBs,
    timeout: TIMEOUT,
    version: '@{version}',

    showMsg: function (type, message, timeout = this.timeout[type]) {
        let self = this;
        let msg = $('#msg');
        if (msg.length === 0) {
            msg = $(`

                    ×

            `);

            msg.find('[data-dismiss="msg"]').on('click', function (e) {
                e.preventDefault();

                self.hideMsg();
            });

            msg.appendTo(document.body);
        }

        msg.find('span').html(message);
        msg.find('i').attr('class', self.icon[type]);

        setTimeout(function () {
            msg.attr('class', 'alert alert-' + type + ' showed');
        }, 50);

        clearTimeout(timer);
        if (timeout > 0) {
            timer = setTimeout(function () {
                self.hideMsg();
            }, timeout);
        }
    },

    info: function (message, timeout) {
        this.showMsg('info', message, timeout);
    },

    success: function (message, timeout) {
        this.showMsg('success', message, timeout);
    },

    warning: function (message, timeout) {
        this.showMsg('warning', message, timeout);
    },

    error: function (message, timeout) {
        this.danger(message, timeout);
    },

    danger: function (message, timeout) {
        this.showMsg('danger', message, timeout);
    },

    hideMsg: function () {
        $('#msg').removeClass('showed');
    }
};
<commit_msg>https://github.com/ducdhm/bootstrap-msg/issues/3<commit_after>import $ from 'jquery';

import TIMEOUT from './defaults/timeout';
import iconBs from './defaults/icon';
import iconFa from './defaults/iconFa';

let timer;
export default {
    ICONS: {
        BOOTSTRAP: iconBs,
        FONTAWESOME: iconFa
    },
    icon: iconFa,
    timeout: TIMEOUT,
    version: '@{version}',

    showMsg: function (type, message, timeout = this.timeout[type]) {
        let self = this;
        let msg = $('#msg');
        if (msg.length === 0) {
            msg = $(`

                    ×

            `);

            msg.find('[data-dismiss="msg"]').on('click', function (e) {
                e.preventDefault();

                self.hideMsg();
            });

            msg.appendTo(document.body);
        }

        msg.find('span').html(message);
        msg.find('i').attr('class', self.icon[type]);

        setTimeout(function () {
            msg.attr('class', 'alert alert-' + type + ' showed');
        }, 50);

        clearTimeout(timer);
        if (timeout > 0) {
            timer = setTimeout(function () {
                self.hideMsg();
            }, timeout);
        }
    },

    info: function (message, timeout) {
        this.showMsg('info', message, timeout);
    },

    success: function (message, timeout) {
        this.showMsg('success', message, timeout);
    },

    warning: function (message, timeout) {
        this.showMsg('warning', message, timeout);
    },

    error: function (message, timeout) {
        this.danger(message, timeout);
    },

    danger: function (message, timeout) {
        this.showMsg('danger', message, timeout);
    },

    hideMsg: function () {
        $('#msg').removeClass('showed');
    }
};
<|endoftext|>


<commit_before>/*
  termap - Terminal Map Viewer
  by Michael Strassburger <codepoet@cpan.org>

  Source for VectorTiles - supports
  * remote TileServer
  * local MBTiles and VectorTiles
*/
'use strict';
const userhome = require('userhome');
const fetch = require('node-fetch');
const fs = require('fs');

const Tile = require('./Tile');
const config = require('./config');

// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
// To maximize MapSCII’s compatibility, MBTiles support must be manually added via
// $> npm install -g mbtiles
let MBTiles = null;
try {
  MBTiles = require('mbtiles');
} catch (err) {void 0;}

const modes = {
  MBTiles: 1,
  VectorTile: 2,
  HTTP: 3,
};

class TileSource {
  init(source) {
    this.source = source;

    this.cache = {};
    this.cacheSize = 16;
    this.cached = [];

    this.mode = null;
    this.mbtiles = null;
    this.styler = null;

    if (this.source.startsWith('http')) {
      if (config.persistDownloadedTiles) {
        this._initPersistence();
      }

      this.mode = modes.HTTP;

    } else if (this.source.endsWith('.mbtiles')) {
      if (!MBTiles) {
        throw new Error('MBTiles support must be installed with following command: \'npm install -g mbtiles\'');
      }

      this.mode = modes.MBTiles;
      this.loadMBtils(source);
    } else {
      throw new Error('source type isn\'t supported yet');
    }
  }

  loadMBtils(source) {
    return new Promise((resolve, reject) => {
      new MBTiles(source, (err, mbtiles) => {
        if (err) {
          reject(err);
        }
        this.mbtiles = mbtiles;
        resolve();
      });
    });
  }

  useStyler(styler) {
    this.styler = styler;
  }

  getTile(z, x, y) {
    if (!this.mode) {
      throw new Error('no TileSource defined');
    }

    const cached = this.cache[[z, x, y].join('-')];
    if (cached) {
      return Promise.resolve(cached);
    }

    if (this.cached.length > this.cacheSize) {
      const overflow = Math.abs(this.cacheSize - this.cache.length);
      for (const tile in this.cached.splice(0, overflow)) {
        delete this.cache[tile];
      }
    }

    switch (this.mode) {
      case modes.MBTiles:
        return this._getMBTile(z, x, y);
      case modes.HTTP:
        return this._getHTTP(z, x, y);
    }
  }

  _getHTTP(z, x, y) {
    let promise;
    const persistedTile = this._getPersited(z, x, y);
    if (config.persistDownloadedTiles && persistedTile) {
      promise = Promise.resolve(persistedTile);
    } else {
      promise = fetch(this.source + [z,x,y].join('/') + '.pbf')
        .then((res) => res.buffer())
        .then((buffer) => {
          if (config.persistDownloadedTiles) {
            this._persistTile(z, x, y, buffer);
            return buffer;
          }
        });
    }
    return promise.then((buffer) => {
      return this._createTile(z, x, y, buffer);
    });
  }

  _getMBTile(z, x, y) {
    return new Promise((resolve, reject) => {
      this.mbtiles.getTile(z, x, y, (err, buffer) => {
        if (err) {
          reject(err);
        }
        resolve(this._createTile(z, x, y, buffer));
      });
    });
  }

  _createTile(z, x, y, buffer) {
    const name = [z, x, y].join('-');
    this.cached.push(name);

    const tile = this.cache[name] = new Tile(this.styler);
    return tile.load(buffer);
  }

  _initPersistence() {
    try {
      this._createFolder(userhome('.mapscii'));
      this._createFolder(userhome('.mapscii', 'cache'));
    } catch (error) {
      config.persistDownloadedTiles = false;
    }
  }

  _persistTile(z, x, y, buffer) {
    const zoom = z.toString();
    this._createFolder(userhome('.mapscii', 'cache', zoom));
    const filePath = userhome('.mapscii', 'cache', zoom, `${x}-${y}.pbf`);
    return fs.writeFile(filePath, buffer, () => null);
  }

  _getPersited(z, x, y) {
    try {
      return fs.readFileSync(userhome('.mapscii', 'cache', z.toString(), `${x}-${y}.pbf`));
    } catch (error) {
      return false;
    }
  }

  _createFolder(path) {
    try {
      fs.mkdirSync(path);
      return true;
    } catch (error) {
      return error.code === 'EEXIST';
    }
  }
}

module.exports = TileSource;
<commit_msg>Fix MBTiles typo MBtils<commit_after>/*
  termap - Terminal Map Viewer
  by Michael Strassburger <codepoet@cpan.org>

  Source for VectorTiles - supports
  * remote TileServer
  * local MBTiles and VectorTiles
*/
'use strict';
const userhome = require('userhome');
const fetch = require('node-fetch');
const fs = require('fs');

const Tile = require('./Tile');
const config = require('./config');

// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
// To maximize MapSCII’s compatibility, MBTiles support must be manually added via
// $> npm install -g mbtiles
let MBTiles = null;
try {
  MBTiles = require('mbtiles');
} catch (err) {void 0;}

const modes = {
  MBTiles: 1,
  VectorTile: 2,
  HTTP: 3,
};

class TileSource {
  init(source) {
    this.source = source;

    this.cache = {};
    this.cacheSize = 16;
    this.cached = [];

    this.mode = null;
    this.mbtiles = null;
    this.styler = null;

    if (this.source.startsWith('http')) {
      if (config.persistDownloadedTiles) {
        this._initPersistence();
      }

      this.mode = modes.HTTP;

    } else if (this.source.endsWith('.mbtiles')) {
      if (!MBTiles) {
        throw new Error('MBTiles support must be installed with following command: \'npm install -g mbtiles\'');
      }

      this.mode = modes.MBTiles;
      this.loadMBTiles(source);
    } else {
      throw new Error('source type isn\'t supported yet');
    }
  }

  loadMBTiles(source) {
    return new Promise((resolve, reject) => {
      new MBTiles(source, (err, mbtiles) => {
        if (err) {
          reject(err);
        }
        this.mbtiles = mbtiles;
        resolve();
      });
    });
  }

  useStyler(styler) {
    this.styler = styler;
  }

  getTile(z, x, y) {
    if (!this.mode) {
      throw new Error('no TileSource defined');
    }

    const cached = this.cache[[z, x, y].join('-')];
    if (cached) {
      return Promise.resolve(cached);
    }

    if (this.cached.length > this.cacheSize) {
      const overflow = Math.abs(this.cacheSize - this.cache.length);
      for (const tile in this.cached.splice(0, overflow)) {
        delete this.cache[tile];
      }
    }

    switch (this.mode) {
      case modes.MBTiles:
        return this._getMBTile(z, x, y);
      case modes.HTTP:
        return this._getHTTP(z, x, y);
    }
  }

  _getHTTP(z, x, y) {
    let promise;
    const persistedTile = this._getPersited(z, x, y);
    if (config.persistDownloadedTiles && persistedTile) {
      promise = Promise.resolve(persistedTile);
    } else {
      promise = fetch(this.source + [z,x,y].join('/') + '.pbf')
        .then((res) => res.buffer())
        .then((buffer) => {
          if (config.persistDownloadedTiles) {
            this._persistTile(z, x, y, buffer);
            return buffer;
          }
        });
    }
    return promise.then((buffer) => {
      return this._createTile(z, x, y, buffer);
    });
  }

  _getMBTile(z, x, y) {
    return new Promise((resolve, reject) => {
      this.mbtiles.getTile(z, x, y, (err, buffer) => {
        if (err) {
          reject(err);
        }
        resolve(this._createTile(z, x, y, buffer));
      });
    });
  }

  _createTile(z, x, y, buffer) {
    const name = [z, x, y].join('-');
    this.cached.push(name);

    const tile = this.cache[name] = new Tile(this.styler);
    return tile.load(buffer);
  }

  _initPersistence() {
    try {
      this._createFolder(userhome('.mapscii'));
      this._createFolder(userhome('.mapscii', 'cache'));
    } catch (error) {
      config.persistDownloadedTiles = false;
    }
  }

  _persistTile(z, x, y, buffer) {
    const zoom = z.toString();
    this._createFolder(userhome('.mapscii', 'cache', zoom));
    const filePath = userhome('.mapscii', 'cache', zoom, `${x}-${y}.pbf`);
    return fs.writeFile(filePath, buffer, () => null);
  }

  _getPersited(z, x, y) {
    try {
      return fs.readFileSync(userhome('.mapscii', 'cache', z.toString(), `${x}-${y}.pbf`));
    } catch (error) {
      return false;
    }
  }

  _createFolder(path) {
    try {
      fs.mkdirSync(path);
      return true;
    } catch (error) {
      return error.code === 'EEXIST';
    }
  }
}

module.exports = TileSource;
<|endoftext|>


<commit_before>import { Model as ObjectionModel } from 'objection'

import './RelationSynchronizer'
import './RelationValidator'
import './inflect'

const as = require('as-type')

export class Model extends ObjectionModel {
	static descriptiveName = null
	static eager = null
	static eagerFilters = null

	static $$app = null

	static app(app) {
		if(!app.isNil) {
			this.$$app = app
		} else {
			return this.$$app
		}
	}

	$app() {
		return this.constructor.app()
	}

	static findById(id) {
		return this.query().findById(id)
	}

	static findByRouteParameter(value) {
		return this.findById(value)
	}

	static routeBind(name, description) {
		description = description || `${name} Value`

		this.app().routes.bind(name, (value, resolve, reject) => {
			this.findByRouteParameter(value).then(row => {
				if(row.isNil) {
					reject(new NotFoundError(`${this.describe()} Not found`))
					return
				}

				resolve(row)
			})
		}, { swagger: { name, description } })
	}

	static buildRelations() {
		return { }
	}

	static getRelations() {
		if(this.relationMappings.isNil) {
			this.relationMappings = this.buildRelations()
		}

		return super.getRelations()
	}

	static hasOne(modelClass, foreignKey = null, localKey = null) {
		return this._hasOneOrMany(this.HasOneRelation, modelClass, foreignKey, localKey)
	}

	static hasMany(modelClass, foreignKey = null, localKey = null) {
		return this._hasOneOrMany(this.HasManyRelation, modelClass, foreignKey, localKey)
	}

	static _hasOneOrMany(relation, modelClass, foreignKey = null, localKey = null) {
		foreignKey = foreignKey || inflect.foreignKey(this.name)

		if(localKey.isNil) {
			localKey = this.getFullIdColumn()
		} else {
			localKey = `${this.tableName}.${localKey}`
		}

		return {
			relation: relation,
			modelClass: modelClass,
			join: {
				from: `${modelClass.tableName}.${foreignKey}`,
				to: localKey
			}
		}
	}

	static belongsTo(modelClass, foreignKey = null, otherKey = null) {
		foreignKey = foreignKey || inflect.foreignKey(modelClass.name)

		if(otherKey.isNil) {
			otherKey = modelClass.getFullIdColumn()
		} else {
			otherKey = `${modelClass.tableName}.${otherKey}`
		}

		return {
			relation: this.BelongsToOneRelation,
			modelClass: modelClass,
			join: {
				from: `${this.tableName}.${foreignKey}`,
				to: otherKey
			}
		}
	}

	static belongsToMany(modelClass, tableName = null, foreignKey = null, otherKey = null) {
		tableName = tableName || [ this.tableName, modelClass.tableName ].sort().join('_')
		foreignKey = foreignKey || inflect.foreignKey(modelClass.name)
		otherKey = otherKey || inflect.foreignKey(this.name)

		return {
			relation: this.ManyToManyRelation,
			modelClass: modelClass,
			join: {
				from: this.getFullIdColumn(),
				through: {
					from: `${tableName}.${otherKey}`,
					to: `${tableName}.${foreignKey}`
				},
				to: modelClass.getFullIdColumn()
			}
		}
	}

	$sync(relation, ids) {
		return (new RelationSynchronizer(this, relation)).sync(ids)
	}

	$relate(relation, ids) {
		return (new RelationSynchronizer(this, relation)).relate(ids)
	}

	$unrelate(relation, ids) {
		return (new RelationSynchronizer(this, relation)).unrelate(ids)
	}

	$parseDatabaseJson(json) {
		json = super.$parseDatabaseJson(json)
		const schema = this.constructor.jsonSchema

		if(schema.isNil || schema.properties.isNil) {
			return json
		}

		for(const field of Object.keys(schema.properties)) {
			if(json[field].isNil) {
				continue
			}

			if(Number.isNaN(json[field])) {
				json[field] = null
				continue
			}

			const property = schema.properties[field]

			// Boolean values can be converted to JSON as "0"/"1", convert back to boolean
			if(property.type === 'boolean') {
				json[field] = as.boolean(json[field])
			}

			// Number values can be converted to JSON as strings, convert back to numbers
			if(property.type === 'number' || property.type === 'float' || property.type === 'double') {
				json[field] = as.float(json[field])
			} else if(property.type === 'integer') {
				json[field] = as.integer(json[field])
			}
		}

		return json
	}

	$beforeValidate(jsonSchema, json, opt) {
		const properties = jsonSchema.properties || { }

		for(const field of Object.keys(properties)) {
			const value = json[field]

			if(value.isNil) {
				continue
			}

			const property = properties[field]
			let fieldType = property.type
			let allowsNull = false

			if(!property.anyOf.isNil) {
				fieldType = property.anyOf.map(type => type.type)
			}

			if(Array.isArray(fieldType) && fieldType.length > 1) {
				allowsNull = fieldType.indexOf('null') >= 0
				fieldType = fieldType.filter(type => type !== 'null')[0]
			}

			if(allowsNull && typeof value === 'string' && value.length === 0) {
				json[field] = null
				continue
			}

			if(fieldType === 'boolean') {
				json[field] = as.boolean(json[field])
			} else if(fieldType === 'integer') {
				json[field] = as.integer(json[field])
			} else if(fieldType === 'number' || fieldType === 'float' || fieldType === 'double') {
				json[field] = as.float(json[field])
			}
		}

		return super.$beforeValidate(jsonSchema, json, opt)
	}

	$beforeSave(inserting) {
		return new Promise(resolve => {
			const now = new Date
			this.updated_at = now

			if(inserting) {
				this.created_at = now
			}

			resolve()
		}).then(() => RelationValidator(this))
	}

	$beforeInsert() {
		return this.$beforeSave(true)
	}

	$beforeUpdate() {
		return this.$beforeSave(false)
	}

	static describe() {
		if(!this.descriptiveName.isNil) {
			return this.descriptiveName
		}

		return inflect.singular(this.tableName)
	}

}
<commit_msg>Added Model.useTimestamps as a way to turn off timestamps<commit_after>import { Model as ObjectionModel } from 'objection'

import './RelationSynchronizer'
import './RelationValidator'
import './inflect'

const as = require('as-type')

export class Model extends ObjectionModel {
	static descriptiveName = null
	static eager = null
	static eagerFilters = null
	static useTimestamps = true

	static $$app = null

	static app(app) {
		if(!app.isNil) {
			this.$$app = app
		} else {
			return this.$$app
		}
	}

	$app() {
		return this.constructor.app()
	}

	static findById(id) {
		return this.query().findById(id)
	}

	static findByRouteParameter(value) {
		return this.findById(value)
	}

	static routeBind(name, description) {
		description = description || `${name} Value`

		this.app().routes.bind(name, (value, resolve, reject) => {
			this.findByRouteParameter(value).then(row => {
				if(row.isNil) {
					reject(new NotFoundError(`${this.describe()} Not found`))
					return
				}

				resolve(row)
			})
		}, { swagger: { name, description } })
	}

	static buildRelations() {
		return { }
	}

	static getRelations() {
		if(this.relationMappings.isNil) {
			this.relationMappings = this.buildRelations()
		}

		return super.getRelations()
	}

	static hasOne(modelClass, foreignKey = null, localKey = null) {
		return this._hasOneOrMany(this.HasOneRelation, modelClass, foreignKey, localKey)
	}

	static hasMany(modelClass, foreignKey = null, localKey = null) {
		return this._hasOneOrMany(this.HasManyRelation, modelClass, foreignKey, localKey)
	}

	static _hasOneOrMany(relation, modelClass, foreignKey = null, localKey = null) {
		foreignKey = foreignKey || inflect.foreignKey(this.name)

		if(localKey.isNil) {
			localKey = this.getFullIdColumn()
		} else {
			localKey = `${this.tableName}.${localKey}`
		}

		return {
			relation: relation,
			modelClass: modelClass,
			join: {
				from: `${modelClass.tableName}.${foreignKey}`,
				to: localKey
			}
		}
	}

	static belongsTo(modelClass, foreignKey = null, otherKey = null) {
		foreignKey = foreignKey || inflect.foreignKey(modelClass.name)

		if(otherKey.isNil) {
			otherKey = modelClass.getFullIdColumn()
		} else {
			otherKey = `${modelClass.tableName}.${otherKey}`
		}

		return {
			relation: this.BelongsToOneRelation,
			modelClass: modelClass,
			join: {
				from: `${this.tableName}.${foreignKey}`,
				to: otherKey
			}
		}
	}

	static belongsToMany(modelClass, tableName = null, foreignKey = null, otherKey = null) {
		tableName = tableName || [ this.tableName, modelClass.tableName ].sort().join('_')
		foreignKey = foreignKey || inflect.foreignKey(modelClass.name)
		otherKey = otherKey || inflect.foreignKey(this.name)

		return {
			relation: this.ManyToManyRelation,
			modelClass: modelClass,
			join: {
				from: this.getFullIdColumn(),
				through: {
					from: `${tableName}.${otherKey}`,
					to: `${tableName}.${foreignKey}`
				},
				to: modelClass.getFullIdColumn()
			}
		}
	}

	$sync(relation, ids) {
		return (new RelationSynchronizer(this, relation)).sync(ids)
	}

	$relate(relation, ids) {
		return (new RelationSynchronizer(this, relation)).relate(ids)
	}

	$unrelate(relation, ids) {
		return (new RelationSynchronizer(this, relation)).unrelate(ids)
	}

	$parseDatabaseJson(json) {
		json = super.$parseDatabaseJson(json)
		const schema = this.constructor.jsonSchema

		if(schema.isNil || schema.properties.isNil) {
			return json
		}

		for(const field of Object.keys(schema.properties)) {
			if(json[field].isNil) {
				continue
			}

			if(Number.isNaN(json[field])) {
				json[field] = null
				continue
			}

			const property = schema.properties[field]

			// Boolean values can be converted to JSON as "0"/"1", convert back to boolean
			if(property.type === 'boolean') {
				json[field] = as.boolean(json[field])
			}

			// Number values can be converted to JSON as strings, convert back to numbers
			if(property.type === 'number' || property.type === 'float' || property.type === 'double') {
				json[field] = as.float(json[field])
			} else if(property.type === 'integer') {
				json[field] = as.integer(json[field])
			}
		}

		return json
	}

	$beforeValidate(jsonSchema, json, opt) {
		const properties = jsonSchema.properties || { }

		for(const field of Object.keys(properties)) {
			const value = json[field]

			if(value.isNil) {
				continue
			}

			const property = properties[field]
			let fieldType = property.type
			let allowsNull = false

			if(!property.anyOf.isNil) {
				fieldType = property.anyOf.map(type => type.type)
			}

			if(Array.isArray(fieldType) && fieldType.length > 1) {
				allowsNull = fieldType.indexOf('null') >= 0
				fieldType = fieldType.filter(type => type !== 'null')[0]
			}

			if(allowsNull && typeof value === 'string' && value.length === 0) {
				json[field] = null
				continue
			}

			if(fieldType === 'boolean') {
				json[field] = as.boolean(json[field])
			} else if(fieldType === 'integer') {
				json[field] = as.integer(json[field])
			} else if(fieldType === 'number' || fieldType === 'float' || fieldType === 'double') {
				json[field] = as.float(json[field])
			}
		}

		return super.$beforeValidate(jsonSchema, json, opt)
	}

	$beforeSave(inserting) {
		return new Promise(resolve => {
			if(this.constructor.useTimestamps) {
				const now = new Date
				this.updated_at = now

				if(inserting) {
					this.created_at = now
				}
			}

			resolve()
		}).then(() => RelationValidator(this))
	}

	$beforeInsert() {
		return this.$beforeSave(true)
	}

	$beforeUpdate() {
		return this.$beforeSave(false)
	}

	static describe() {
		if(!this.descriptiveName.isNil) {
			return this.descriptiveName
		}

		return inflect.singular(this.tableName)
	}

}
<|endoftext|>