<commit_before>angular.module('schemaForm').config(
    ['schemaFormProvider', 'schemaFormDecoratorsProvider', 'sfPathProvider',
        function (schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) {

            var select = function (name, schema, options) {
                if ((schema.type === 'string') && ("enum" in schema)) {
                    var f = schemaFormProvider.stdFormObj(name, schema, options);
                    f.key = options.path;
                    f.type = 'strapselect';
                    options.lookup[sfPathProvider.stringify(options.path)] = f;
                    return f;
                }
            };

            schemaFormProvider.defaults.string.unshift(select);

            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselect',
                'directives/decorators/bootstrap/strap/strapselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselect',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselectdynamic',
                'directives/decorators/bootstrap/strap/strapselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselectdynamic',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            // UI SELECT
            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselect',
                'directives/decorators/bootstrap/uiselect/uiselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselectmultiple',
                'directives/decorators/bootstrap/uiselect/uiselectmultiple.html');

        }])
  .directive("toggleSingleModel", function() {
    // some how we get this to work ...
    return {
      require: 'ngModel',
      restrict: "A",
      scope: {},
      replace: true,
      controller: ['$scope', function($scope)  {
        $scope.$parent.$watch('select_model.selected',function(){
          if($scope.$parent.select_model.selected != undefined) {
            $scope.$parent.insideModel = $scope.$parent.select_model.selected.value;
            $scope.$parent.ngModel.$setViewValue($scope.$parent.select_model.selected.value);
          }
        });
      }]
    };
  })

  .directive('multipleOn', function() {
    return {
    link: function($scope, $element, $attrs) {
        $scope.$watch(
            function () { return $element.attr('multiple-on'); },
            function (newVal) {

                if(newVal == "true") {
                    var select_scope = angular.element($element).scope().$$childTail;
                    select_scope.$isMultiple = true;
                    select_scope.options.multiple = true;
                    select_scope.$select.$element.addClass('select-multiple');
                }
                else {
                    angular.element($element).scope().$$childTail.$isMultiple = false;
                }
            }
        );
      }
    };
  })
  .filter('whereMulti', function() {
    return function(items, key, values) {
      var out = [];

      if (angular.isArray(values) && items !== undefined) {
          values.forEach(function (value) {
              for (var i = 0; i < items.length; i++) {
                  if (value == items[i][key]) {
                      out.push(items[i]);
                      break;
                  }
              }
          });
      } else {
        // Let the output be the input untouched
        out = items;
      }

      return out;
    };
  })
  .filter('propsFilter', function() {
        return function (items, props) {
            var out = [];

            if (angular.isArray(items)) {
                items.forEach(function (item) {
                    var itemMatches = false;

                    var keys = Object.keys(props);
                    for (var i = 0; i < keys.length; i++) {
                        var prop = keys[i];
                        if (item.hasOwnProperty(prop)) {
                            //only match if this property is actually in the item to avoid
                            var text = props[prop].toLowerCase();
                            //search for either a space before the text or the textg at the start of the string so that the middle of words are not matched
                            if (item[prop].toString().toLowerCase().indexOf(text) === 0 || ( item[prop].toString()).toLowerCase().indexOf(' ' + text) !== -1) {
                                itemMatches = true;
                                break;
                            }
                        }
                    }

                    if (itemMatches) {
                        out.push(item);
                    }
                });
            } else {
                // Let the output be the input untouched
                out = items;
            }

            return out;
        };
    });

angular.module('schemaForm').controller('dynamicSelectController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {

    if (!$scope.form.options) {
        $scope.form.options = {};
    }

    $scope.select_model = {};

    console.log("Setting options." + $scope.form.options.toString());
    $scope.form.options.scope = $scope;

    $scope.triggerTitleMap = function () {
        console.log("listener triggered");
        // Ugly workaround to trigger titleMap expression re-evaluation so that the selectFilter it reapplied.
        $scope.form.titleMap.push({"value": "345890u340598u3405u9", "name": "34095u3p4ouij"})
        $timeout(function () { $scope.form.titleMap.pop() })

    };

    $scope.initFiltering = function (localModel) {
        if ($scope.form.options.filterTriggers) {
            $scope.form.options.filterTriggers.forEach(function (trigger) {
                $scope.$parent.$watch(trigger, $scope.triggerTitleMap)

            });
        }
        // This is set here, as the model value may become unitialized and typeless if validation fails.
        $scope.localModelType =  Object.prototype.toString.call(localModel);
        $scope.filteringInitialized = true;
    };

    $scope.finalizeTitleMap = function (form, data, newOptions) {
        // Remap the data

        form.titleMap = [];

        if (newOptions && "map" in newOptions && newOptions .map) {
            var current_row = null,
            final = newOptions.map.nameProperty.length - 1,
            separator = newOptions.map.separatorValue ? newOptions.map.separatorValue : ' - ';
                data.forEach(function (current_row) {
                current_row["value"] = current_row[newOptions .map.valueProperty];
                //check if the value passed is a string or not
                if(typeof newOptions.map.nameProperty != 'string'){
                    //loop through the object/array
                    var newName = "";
                    for (var i in newOptions.map.nameProperty) {
                        newName += current_row[newOptions .map.nameProperty[i]];
                        if(i != final){newName += separator};
                    }
                    current_row["name"] = newName; //init the 'name' property
                }
                else{
                    //if it is a string
                    current_row["name"] = current_row[newOptions .map.nameProperty];
                }
                form.titleMap.push(current_row);
            });

        }
        else {
            data.forEach(function (item) {
                    if ("text" in item) {
                        item.name = item.text
                    }
                }
            );
            form.titleMap = data;
        }

        if ($scope.insideModel && $scope.select_model.selected === undefined) {
          $scope.select_model.selected = $scope.find_in_titleMap($scope.insideModel);
        }

        // The ui-selects needs to be reinitialized (UI select sets the internalModel and externalModel.
        if ($scope.internalModel) {
            console.log("Call uiMultiSelectInitInternalModel");
            $scope.uiMultiSelectInitInternalModel($scope.externalModel);
        }
    };

    $scope.clone = function (obj) {
        // Clone an object (except references to this scope)
        if (null == obj || "object" != typeof(obj)) return obj;

        var copy = obj.constructor();
        for (var attr in obj) {
            // Do not clone if it is this scope
            if (obj[attr] != $scope) {
                if (obj.hasOwnProperty(attr)) copy[attr] = $scope.clone(obj[attr]);
            }
        }
        return copy;
    };

    $scope.getCallback = function (callback) {
        if (typeof(callback) == "string") {
            var _result = $scope.$parent.evalExpr(callback);
            if (typeof(_result) == "function") {
                return _result;
            }
            else {
                throw("A callback string must match name of a function in the parent scope")
            }

        }
        else if (typeof(callback) == "function") {
            return callback;
        }
        else {
            throw("A callback must either be a string matching the name of a function in the parent scope or a " +
            "direct function reference")

        }
    };

    $scope.getOptions = function (options, search) {
        // If defined, let the a callback function manipulate the options
        if (options.httpPost && options.httpPost.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpPost.optionsCallback)(newOptionInstance, search);
        }
        if (options.httpGet && options.httpGet.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpGet.optionsCallback)(newOptionInstance, search);
        }
        else {
            return options;
        }
    };

    $scope.test = function (form) {
        form.titleMap.pop();
    };

    $scope.populateTitleMap = function (form, search) {

        if (form.schema && "enum" in form.schema) {
            form.titleMap = [];
            form.schema.enum.forEach(function (item) {
                    form.titleMap.push({"value": item, "name": item})
                }
            );

        }
        else if (!form.options) {

            console.log("dynamicSelectController.populateTitleMap(key:" + form.key + ") : No options set, needed for dynamic selects");
        }
        else if (form.options.callback) {
            form.titleMap = $scope.getCallback(form.options.callback)(form.options, search);
            $scope.finalizeTitleMap(form,form.titleMap, form.options);
            console.log("callback items: ", form.titleMap);
        }
        else if (form.options.asyncCallback) {
            return $scope.getCallback(form.options.asyncCallback)(form.options, search).then(
                function (_data) {
                    // In order to work with both $http and generic promises
                    _data = _data.data || _data;
                    $scope.finalizeTitleMap(form, _data, form.options);
                    console.log('asyncCallback items', form.titleMap);
                },
                function (data, status) {
                    alert("Loading select items failed(Options: '" + String(form.options) +
                    "\nError: " + status);
                });
        }
        else if (form.options.httpPost) {
            var finalOptions = $scope.getOptions(form.options, search);

            return $http.post(finalOptions.httpPost.url, finalOptions.httpPost.parameter).then(
                function (_data) {

                    $scope.finalizeTitleMap(form, _data.data, finalOptions);
                    console.log('httpPost items', form.titleMap);
                },
                function (data, status) {
                    alert("Loading select items failed (URL: '" + String(finalOptions.httpPost.url) +
                    "' Parameter: " + String(finalOptions.httpPost.parameter) + "\nError: " + status);
                });
        }
        else if (form.options.httpGet) {
            var finalOptions = $scope.getOptions(form.options, search);
            return $http.get(finalOptions.httpGet.url, finalOptions.httpGet.parameter).then(
                function (data) {
                    $scope.finalizeTitleMap(form, data.data, finalOptions);
                    console.log('httpGet items', form.titleMap);
                },
                function (data, status) {
                    alert("Loading select items failed (URL: '" + String(finalOptions.httpGet.url) +
                    "\nError: " + status);
                });
        }
    };

    $scope.find_in_titleMap = function (value) {
        for (i = 0; i < $scope.form.titleMap.length; i++) {
            if ($scope.form.titleMap[i].value == value) {
                return {"value": $scope.form.titleMap[i].value, "name": $scope.form.titleMap[i].name}
            }
        }

    };

    $scope.uiMultiSelectInitInternalModel = function(supplied_model)
    {

        console.log("$scope.externalModel: Key: " +$scope.form.key.toString() + " Model: " + supplied_model.toString());
        $scope.externalModel = supplied_model;
        $scope.internalModel = [];
        if ($scope.form.titleMap) {
            if (supplied_model !== undefined && angular.isArray(supplied_model)){
                supplied_model.forEach(function (value) {
                        $scope.internalModel.push($scope.find_in_titleMap(value));
                    }
                )
            }
        }
    };

}]);

angular.module('schemaForm').filter('selectFilter', [function ($filter) {
    return function (inputArray, controller, localModel, strLocalModel) {
        // As the controllers' .model is the global and its form is the local, we need to get the local model as well.
        // We also need tp be able to set it if is undefined after a validation failure,so for that we need
        // its string representation as well as we do not know its name. A typical value if strLocalModel is model['groups']
        // This is very ugly, though. TODO: Find out why the model is set to undefined after validation failure.

        if (!angular.isDefined(inputArray) || !angular.isDefined(controller.form.options) ||
            !angular.isDefined(controller.form.options.filter) || controller.form.options.filter == '') {
            return inputArray;
        }

        console.log("----- In filtering for " + controller.form.key + "(" + controller.form.title +"), model value: " + JSON.stringify( localModel) + "----");
        console.log("Filter:" + controller.form.options.filter);
        if (!controller.filteringInitialized) {
            console.log("Initialize filter");
            controller.initFiltering(localModel);
        }

        var data = [];

        angular.forEach(inputArray, function (curr_item) {
            //console.log("Compare: curr_item: " + JSON.stringify(curr_item) +
            //"with : " + JSON.stringify( controller.$eval(controller.form.options.filterTriggers[0])));
            if (controller.$eval(controller.form.options.filter, {item: curr_item})) {
                data.push(curr_item);
            }
            else if (localModel) {
                // If not in list, also remove the set value

                if (controller.localModelType == "[object Array]" && localModel.indexOf(curr_item.value) > -1) {
                    localModel.splice(localModel.indexOf(curr_item.value), 1);
                }
                else if (localModel == curr_item.value) {
                    console.log("Setting model of type " + controller.localModelType  + "to null.");
                    localModel = null;
                }
            }
        });

        if (controller.localModelType == "[object Array]" && !localModel) {
            // An undefined local model seems to mess up bootstrap select's indicators
            console.log("Resetting model of type " + controller.localModelType  + " to [].");

            controller.$eval(strLocalModel + "=[]");
        }

        //console.log("Input: " + JSON.stringify(inputArray));
        //console.log("Output: " + JSON.stringify(data));
        //console.log("Model value out : " + JSON.stringify(localModel));
        console.log("----- Exiting filter for " + controller.form.title + "-----");

        return data;
    };
}]);
<commit_msg>add initial value for plain list<commit_after>angular.module('schemaForm').config(
    ['schemaFormProvider', 'schemaFormDecoratorsProvider', 'sfPathProvider',
        function (schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) {

            var select = function (name, schema, options) {
                if ((schema.type === 'string') && ("enum" in schema)) {
                    var f = schemaFormProvider.stdFormObj(name, schema, options);
                    f.key = options.path;
                    f.type = 'strapselect';
                    options.lookup[sfPathProvider.stringify(options.path)] = f;
                    return f;
                }
            };

            schemaFormProvider.defaults.string.unshift(select);

            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselect',
                'directives/decorators/bootstrap/strap/strapselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselect',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselectdynamic',
                'directives/decorators/bootstrap/strap/strapselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselectdynamic',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            // UI SELECT
            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselect',
                'directives/decorators/bootstrap/uiselect/uiselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselectmultiple',
                'directives/decorators/bootstrap/uiselect/uiselectmultiple.html');

        }])
  .directive("toggleSingleModel", function() {
    // some how we get this to work ...
    return {
      require: 'ngModel',
      restrict: "A",
      scope: {},
      replace: true,
      controller: ['$scope', function($scope)  {
        $scope.$parent.$watch('select_model.selected',function(){
          if($scope.$parent.select_model.selected != undefined) {
            $scope.$parent.insideModel = $scope.$parent.select_model.selected.value;
            $scope.$parent.ngModel.$setViewValue($scope.$parent.select_model.selected.value);
          }
        });
      }]
    };
  })

  .directive('multipleOn', function() {
    return {
    link: function($scope, $element, $attrs) {
        $scope.$watch(
            function () { return $element.attr('multiple-on'); },
            function (newVal) {

                if(newVal == "true") {
                    var select_scope = angular.element($element).scope().$$childTail;
                    select_scope.$isMultiple = true;
                    select_scope.options.multiple = true;
                    select_scope.$select.$element.addClass('select-multiple');
                }
                else {
                    angular.element($element).scope().$$childTail.$isMultiple = false;
                }
            }
        );
      }
    };
  })
  .filter('whereMulti', function() {
    return function(items, key, values) {
      var out = [];

      if (angular.isArray(values) && items !== undefined) {
          values.forEach(function (value) {
              for (var i = 0; i < items.length; i++) {
                  if (value == items[i][key]) {
                      out.push(items[i]);
                      break;
                  }
              }
          });
      } else {
        // Let the output be the input untouched
        out = items;
      }

      return out;
    };
  })
  .filter('propsFilter', function() {
        return function (items, props) {
            var out = [];

            if (angular.isArray(items)) {
                items.forEach(function (item) {
                    var itemMatches = false;

                    var keys = Object.keys(props);
                    for (var i = 0; i < keys.length; i++) {
                        var prop = keys[i];
                        if (item.hasOwnProperty(prop)) {
                            //only match if this property is actually in the item to avoid
                            var text = props[prop].toLowerCase();
                            //search for either a space before the text or the textg at the start of the string so that the middle of words are not matched
                            if (item[prop].toString().toLowerCase().indexOf(text) === 0 || ( item[prop].toString()).toLowerCase().indexOf(' ' + text) !== -1) {
                                itemMatches = true;
                                break;
                            }
                        }
                    }

                    if (itemMatches) {
                        out.push(item);
                    }
                });
            } else {
                // Let the output be the input untouched
                out = items;
            }

            return out;
        };
    });

angular.module('schemaForm').controller('dynamicSelectController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {

    if (!$scope.form.options) {
        $scope.form.options = {};
    }

    $scope.select_model = {};

    console.log("Setting options." + $scope.form.options.toString());
    $scope.form.options.scope = $scope;

    $scope.triggerTitleMap = function () {
        console.log("listener triggered");
        // Ugly workaround to trigger titleMap expression re-evaluation so that the selectFilter it reapplied.
        $scope.form.titleMap.push({"value": "345890u340598u3405u9", "name": "34095u3p4ouij"})
        $timeout(function () { $scope.form.titleMap.pop() })

    };

    $scope.initFiltering = function (localModel) {
        if ($scope.form.options.filterTriggers) {
            $scope.form.options.filterTriggers.forEach(function (trigger) {
                $scope.$parent.$watch(trigger, $scope.triggerTitleMap)

            });
        }
        // This is set here, as the model value may become unitialized and typeless if validation fails.
        $scope.localModelType =  Object.prototype.toString.call(localModel);
        $scope.filteringInitialized = true;
    };

    $scope.finalizeTitleMap = function (form, data, newOptions) {
        // Remap the data

        form.titleMap = [];

        if (newOptions && "map" in newOptions && newOptions .map) {
            var current_row = null,
            final = newOptions.map.nameProperty.length - 1,
            separator = newOptions.map.separatorValue ? newOptions.map.separatorValue : ' - ';
                data.forEach(function (current_row) {
                current_row["value"] = current_row[newOptions .map.valueProperty];
                //check if the value passed is a string or not
                if(typeof newOptions.map.nameProperty != 'string'){
                    //loop through the object/array
                    var newName = "";
                    for (var i in newOptions.map.nameProperty) {
                        newName += current_row[newOptions .map.nameProperty[i]];
                        if(i != final){newName += separator};
                    }
                    current_row["name"] = newName; //init the 'name' property
                }
                else{
                    //if it is a string
                    current_row["name"] = current_row[newOptions .map.nameProperty];
                }
                form.titleMap.push(current_row);
            });

        }
        else {
            data.forEach(function (item) {
                    if ("text" in item) {
                        item.name = item.text
                    }
                }
            );
            form.titleMap = data;
        }

        if ($scope.insideModel && $scope.select_model.selected === undefined) {
          $scope.select_model.selected = $scope.find_in_titleMap($scope.insideModel);
        }

        // The ui-selects needs to be reinitialized (UI select sets the internalModel and externalModel.
        if ($scope.internalModel) {
            console.log("Call uiMultiSelectInitInternalModel");
            $scope.uiMultiSelectInitInternalModel($scope.externalModel);
        }
    };

    $scope.clone = function (obj) {
        // Clone an object (except references to this scope)
        if (null == obj || "object" != typeof(obj)) return obj;

        var copy = obj.constructor();
        for (var attr in obj) {
            // Do not clone if it is this scope
            if (obj[attr] != $scope) {
                if (obj.hasOwnProperty(attr)) copy[attr] = $scope.clone(obj[attr]);
            }
        }
        return copy;
    };

    $scope.getCallback = function (callback) {
        if (typeof(callback) == "string") {
            var _result = $scope.$parent.evalExpr(callback);
            if (typeof(_result) == "function") {
                return _result;
            }
            else {
                throw("A callback string must match name of a function in the parent scope")
            }

        }
        else if (typeof(callback) == "function") {
            return callback;
        }
        else {
            throw("A callback must either be a string matching the name of a function in the parent scope or a " +
            "direct function reference")

        }
    };

    $scope.getOptions = function (options, search) {
        // If defined, let the a callback function manipulate the options
        if (options.httpPost && options.httpPost.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpPost.optionsCallback)(newOptionInstance, search);
        }
        if (options.httpGet && options.httpGet.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpGet.optionsCallback)(newOptionInstance, search);
        }
        else {
            return options;
        }
    };

    $scope.test = function (form) {
        form.titleMap.pop();
    };

    $scope.populateTitleMap = function (form, search) {

        if (form.schema && "enum" in form.schema) {
            form.titleMap = [];
            form.schema.enum.forEach(function (item) {
                    form.titleMap.push({"value": item, "name": item})
                }
            );

        }
        else if (!form.options) {

            console.log("dynamicSelectController.populateTitleMap(key:" + form.key + ") : No options set, needed for dynamic selects");
        }
        else if (form.options.callback) {
            form.titleMap = $scope.getCallback(form.options.callback)(form.options, search);
            $scope.finalizeTitleMap(form,form.titleMap, form.options);
            console.log("callback items: ", form.titleMap);
        }
        else if (form.options.asyncCallback) {
            return $scope.getCallback(form.options.asyncCallback)(form.options, search).then(
                function (_data) {
                    // In order to work with both $http and generic promises
                    _data = _data.data || _data;
                    $scope.finalizeTitleMap(form, _data, form.options);
                    console.log('asyncCallback items', form.titleMap);
                },
                function (data, status) {
                    alert("Loading select items failed(Options: '" + String(form.options) +
                    "\nError: " + status);
                });
        }
        else if (form.options.httpPost) {
            var finalOptions = $scope.getOptions(form.options, search);

            return $http.post(finalOptions.httpPost.url, finalOptions.httpPost.parameter).then(
                function (_data) {

                    $scope.finalizeTitleMap(form, _data.data, finalOptions);
                    console.log('httpPost items', form.titleMap);
                },
                function (data, status) {
                    alert("Loading select items failed (URL: '" + String(finalOptions.httpPost.url) +
                    "' Parameter: " + String(finalOptions.httpPost.parameter) + "\nError: " + status);
                });
        }
        else if (form.options.httpGet) {
            var finalOptions = $scope.getOptions(form.options, search);
            return $http.get(finalOptions.httpGet.url, finalOptions.httpGet.parameter).then(
                function (data) {
                    $scope.finalizeTitleMap(form, data.data, finalOptions);
                    console.log('httpGet items', form.titleMap);
                },
                function (data, status) {
                    alert("Loading select items failed (URL: '" + String(finalOptions.httpGet.url) +
                    "\nError: " + status);
                });
        }
        else {
            if ($scope.insideModel && $scope.select_model.selected === undefined) {
                $scope.select_model.selected = $scope.find_in_titleMap($scope.insideModel);
            }
        }
    };

    $scope.find_in_titleMap = function (value) {
        for (i = 0; i < $scope.form.titleMap.length; i++) {
            if ($scope.form.titleMap[i].value == value) {
                return {"value": $scope.form.titleMap[i].value, "name": $scope.form.titleMap[i].name}
            }
        }

    };

    $scope.uiMultiSelectInitInternalModel = function(supplied_model)
    {

        console.log("$scope.externalModel: Key: " +$scope.form.key.toString() + " Model: " + supplied_model.toString());
        $scope.externalModel = supplied_model;
        $scope.internalModel = [];
        if ($scope.form.titleMap) {
            if (supplied_model !== undefined && angular.isArray(supplied_model)){
                supplied_model.forEach(function (value) {
                        $scope.internalModel.push($scope.find_in_titleMap(value));
                    }
                )
            }
        }
    };

}]);

angular.module('schemaForm').filter('selectFilter', [function ($filter) {
    return function (inputArray, controller, localModel, strLocalModel) {
        // As the controllers' .model is the global and its form is the local, we need to get the local model as well.
        // We also need tp be able to set it if is undefined after a validation failure,so for that we need
        // its string representation as well as we do not know its name. A typical value if strLocalModel is model['groups']
        // This is very ugly, though. TODO: Find out why the model is set to undefined after validation failure.

        if (!angular.isDefined(inputArray) || !angular.isDefined(controller.form.options) ||
            !angular.isDefined(controller.form.options.filter) || controller.form.options.filter == '') {
            return inputArray;
        }

        console.log("----- In filtering for " + controller.form.key + "(" + controller.form.title +"), model value: " + JSON.stringify( localModel) + "----");
        console.log("Filter:" + controller.form.options.filter);
        if (!controller.filteringInitialized) {
            console.log("Initialize filter");
            controller.initFiltering(localModel);
        }

        var data = [];

        angular.forEach(inputArray, function (curr_item) {
            //console.log("Compare: curr_item: " + JSON.stringify(curr_item) +
            //"with : " + JSON.stringify( controller.$eval(controller.form.options.filterTriggers[0])));
            if (controller.$eval(controller.form.options.filter, {item: curr_item})) {
                data.push(curr_item);
            }
            else if (localModel) {
                // If not in list, also remove the set value

                if (controller.localModelType == "[object Array]" && localModel.indexOf(curr_item.value) > -1) {
                    localModel.splice(localModel.indexOf(curr_item.value), 1);
                }
                else if (localModel == curr_item.value) {
                    console.log("Setting model of type " + controller.localModelType  + "to null.");
                    localModel = null;
                }
            }
        });

        if (controller.localModelType == "[object Array]" && !localModel) {
            // An undefined local model seems to mess up bootstrap select's indicators
            console.log("Resetting model of type " + controller.localModelType  + " to [].");

            controller.$eval(strLocalModel + "=[]");
        }

        //console.log("Input: " + JSON.stringify(inputArray));
        //console.log("Output: " + JSON.stringify(data));
        //console.log("Model value out : " + JSON.stringify(localModel));
        console.log("----- Exiting filter for " + controller.form.title + "-----");

        return data;
    };
}]);
<|endoftext|>


<commit_before>/*
 * Author: CM
 */
(function($) {
  /**
   * @param {jQuery} $elem
   * @constructor
   */
  function ClipSlide($elem) {
    /** @type {jQuery} */
    this.$elem = $elem;
    /** @type {jQuery} */
    this.$content = this.$elem.children();
    /** @type {jQuery} */
    this.$handle = null;

    this.$elem.addClass('clipSlide');
    this.$content.css({display: 'block'});
    this.toggle(false);
  }

  /**
   * @param {Boolean} [state]
   */
  ClipSlide.prototype.toggle = function(state) {
    var $elem = this.$elem;
    var self = this;

    if (state) {
      if ($elem.height() < this.$content.outerHeight(true)) {
        if (!this.isEnabled()) {
          this.enable();
        }
        $elem.height($elem.height());
        $elem.css('max-height', 'none');
        $elem.animate({
          'height': this.$content.outerHeight(true)
        }, 'fast', function() {
          self.$handle.toggle(false);
          $elem.css('height', 'auto');
        });
      }
    } else {
      if ($elem.height() < this.$content.outerHeight(true)) {
        if (!this.isEnabled()) {
          this.enable();
        }
        this.$handle.toggle(true);
      } else {
        $elem.trigger('toggle.clipSlide');
      }
    }
  };

  ClipSlide.prototype.isEnabled = function() {
    return this.$elem.hasClass('clipSlide-enabled');
  };

  ClipSlide.prototype.enable = function() {
    this.$elem.addClass('clipSlide-enabled');
    this.$handle = $('

').appendTo(this.$elem);
    var self = this;
    this.$handle.on('click.clipSlide', function() {
      self.toggle(true);
      self.$elem.trigger('toggle.clipSlide');
    });
  };

  $.fn.clipSlide = function(action, value) {
    return this.each(function() {
      var $self = $(this);
      var instance = $self.data('clipSlide');
      if (!instance) {
        instance = new ClipSlide($self);
        $self.data('clipSlide', instance);
      }

      switch (action) {
        case 'toggle':
          instance.toggle(value);
          break;
        default:
          break;
      }
    });
  };
})(jQuery);
<commit_msg>Defer toggling of ClipSlide.<commit_after>/*
 * Author: CM
 */
(function($) {
  /**
   * @param {jQuery} $elem
   * @constructor
   */
  function ClipSlide($elem) {
    /** @type {jQuery} */
    this.$elem = $elem;
    /** @type {jQuery} */
    this.$content = this.$elem.children();
    /** @type {jQuery} */
    this.$handle = null;

    this.$elem.addClass('clipSlide');
    this.$content.css({display: 'block'});

    var self = this;
    _.defer(function() {
      self.toggle(false);
    });
  }

  /**
   * @param {Boolean} [state]
   */
  ClipSlide.prototype.toggle = function(state) {
    var $elem = this.$elem;
    var self = this;

    if (state) {
      if ($elem.height() < this.$content.outerHeight(true)) {
        if (!this.isEnabled()) {
          this.enable();
        }
        $elem.height($elem.height());
        $elem.css('max-height', 'none');
        $elem.animate({
          'height': this.$content.outerHeight(true)
        }, 'fast', function() {
          self.$handle.toggle(false);
          $elem.css('height', 'auto');
        });
      }
    } else {
      if ($elem.height() < this.$content.outerHeight(true)) {
        if (!this.isEnabled()) {
          this.enable();
        }
        this.$handle.toggle(true);
      } else {
        $elem.trigger('toggle.clipSlide');
      }
    }
  };

  ClipSlide.prototype.isEnabled = function() {
    return this.$elem.hasClass('clipSlide-enabled');
  };

  ClipSlide.prototype.enable = function() {
    this.$elem.addClass('clipSlide-enabled');
    this.$handle = $('

').appendTo(this.$elem);
    var self = this;
    this.$handle.on('click.clipSlide', function() {
      self.toggle(true);
      self.$elem.trigger('toggle.clipSlide');
    });
  };

  $.fn.clipSlide = function(action, value) {
    return this.each(function() {
      var $self = $(this);
      var instance = $self.data('clipSlide');
      if (!instance) {
        instance = new ClipSlide($self);
        $self.data('clipSlide', instance);
      }

      switch (action) {
        case 'toggle':
          instance.toggle(value);
          break;
        default:
          break;
      }
    });
  };
})(jQuery);
<|endoftext|>


<commit_before>Ti.Map = require('ti.map');

//Application Window Component Constructor
exports.ApplicationWindow = function() {
function isiOS7Plus()
{
	// iOS-specific test
	if (Titanium.Platform.name == 'iPhone OS')
	{
		var version = Titanium.Platform.version.split(".");
		var major = parseInt(version[0],10);

		// Can only test this support on a 3.2+ device
		if (major >= 7)
		{
			return true;
		}
	}
	return false;
}

var iOS7 = isiOS7Plus();
var theTop = iOS7 ? 20 : 0;

	var geo = require('geo');

	//create object instance
	var self = Ti.UI.createWindow({
		backgroundColor: '#800'
	});

	// create UI components
	var view = Ti.UI.createView({
		backgroundColor: '#800',
		top: theTop
	});
	var textfield = Ti.UI.createTextField({
		height: '40dp',
		top: '5dp',
		left: '5dp',
		right: '50dp',
		style: Ti.UI.INPUT_BORDERSTYLE_ROUNDED,
		hintText: 'Enter an address',
		backgroundColor: '#fff',
		paddingLeft: '5dp'
	});
	var button = Ti.UI.createButton({
		title: '+',
		font: {
			fontSize: '20dp',
			fontWeight: 'bold'
		},
		top: '5dp',
		height: '40dp',
		width: '40dp',
		right: '5dp'
	});
	var mapview;

	// add map after window opens
	self.addEventListener('open', function() {
		// Make sure we only add the map once
		if (mapview !== undefined) {
			return;
		}

		mapview = Titanium.Map.createView({
			mapType: Titanium.Map.STANDARD_TYPE,
			region: {
				latitude: geo.LATITUDE_BASE,
				longitude: geo.LONGITUDE_BASE,
				latitudeDelta: 0.1,
				longitudeDelta: 0.1
			},
			animate: true,
			regionFit: true,
			userLocation: false,
			top: '70dp'
		});

		// Add initial annotation
		mapview.addAnnotation(Ti.Map.createAnnotation({
			animate: true,
			pincolor: Titanium.Map.ANNOTATION_RED,
			title: 'Appcelerator',
			latitude: geo.LATITUDE_BASE,
			longitude: geo.LONGITUDE_BASE,
			leftButton: '/images/delete.png'
		}));

		// Handle all map annotation clicks
		mapview.addEventListener('click', function(e) {
			if (e.annotation && (e.clicksource === 'leftButton' || e.clicksource == 'leftPane')) {
				mapview.removeAnnotation(e.annotation);
			}
		});
		self.add(mapview);
	});

	// Execute forward geocode on button click
	button.addEventListener('click', function() {
		textfield.blur();
		geo.forwardGeocode(textfield.value, function(geodata) {
			mapview.addAnnotation(Ti.Map.createAnnotation({
				animate: true,
				pincolor: Titanium.Map.ANNOTATION_RED,
				title: geodata.title,
				latitude: geodata.coords.latitude,
				longitude: geodata.coords.longitude,
				leftButton: '/images/delete.png'
			}));
			mapview.setLocation({
				latitude: geodata.coords.latitude,
				longitude: geodata.coords.longitude,
				latitudeDelta: 1,
				longitudeDelta: 1
			});
		});
	});

	// assemble view hierarchy
	view.add(textfield);
	view.add(button);
	self.add(view);

	return self;
};
<commit_msg>timob-15920: update constant.<commit_after>Ti.Map = require('ti.map');

//Application Window Component Constructor
exports.ApplicationWindow = function() {
function isiOS7Plus()
{
	// iOS-specific test
	if (Titanium.Platform.name == 'iPhone OS')
	{
		var version = Titanium.Platform.version.split(".");
		var major = parseInt(version[0],10);

		// Can only test this support on a 3.2+ device
		if (major >= 7)
		{
			return true;
		}
	}
	return false;
}

var iOS7 = isiOS7Plus();
var theTop = iOS7 ? 20 : 0;

	var geo = require('geo');

	//create object instance
	var self = Ti.UI.createWindow({
		backgroundColor: '#800'
	});

	// create UI components
	var view = Ti.UI.createView({
		backgroundColor: '#800',
		top: theTop
	});
	var textfield = Ti.UI.createTextField({
		height: '40dp',
		top: '5dp',
		left: '5dp',
		right: '50dp',
		style: Ti.UI.INPUT_BORDERSTYLE_ROUNDED,
		hintText: 'Enter an address',
		backgroundColor: '#fff',
		paddingLeft: '5dp'
	});
	var button = Ti.UI.createButton({
		title: '+',
		font: {
			fontSize: '20dp',
			fontWeight: 'bold'
		},
		top: '5dp',
		height: '40dp',
		width: '40dp',
		right: '5dp'
	});
	var mapview;

	// add map after window opens
	self.addEventListener('open', function() {
		// Make sure we only add the map once
		if (mapview !== undefined) {
			return;
		}

		mapview = Titanium.Map.createView({
			mapType: Titanium.Map.NORMAL_TYPE,
			region: {
				latitude: geo.LATITUDE_BASE,
				longitude: geo.LONGITUDE_BASE,
				latitudeDelta: 0.1,
				longitudeDelta: 0.1
			},
			animate: true,
			regionFit: true,
			userLocation: false,
			top: '70dp'
		});

		// Add initial annotation
		mapview.addAnnotation(Ti.Map.createAnnotation({
			animate: true,
			pincolor: Titanium.Map.ANNOTATION_RED,
			title: 'Appcelerator',
			latitude: geo.LATITUDE_BASE,
			longitude: geo.LONGITUDE_BASE,
			leftButton: '/images/delete.png'
		}));

		// Handle all map annotation clicks
		mapview.addEventListener('click', function(e) {
			if (e.annotation && (e.clicksource === 'leftButton' || e.clicksource == 'leftPane')) {
				mapview.removeAnnotation(e.annotation);
			}
		});
		self.add(mapview);
	});

	// Execute forward geocode on button click
	button.addEventListener('click', function() {
		textfield.blur();
		geo.forwardGeocode(textfield.value, function(geodata) {
			mapview.addAnnotation(Ti.Map.createAnnotation({
				animate: true,
				pincolor: Titanium.Map.ANNOTATION_RED,
				title: geodata.title,
				latitude: geodata.coords.latitude,
				longitude: geodata.coords.longitude,
				leftButton: '/images/delete.png'
			}));
			mapview.setLocation({
				latitude: geodata.coords.latitude,
				longitude: geodata.coords.longitude,
				latitudeDelta: 1,
				longitudeDelta: 1
			});
		});
	});

	// assemble view hierarchy
	view.add(textfield);
	view.add(button);
	self.add(view);

	return self;
};
<|endoftext|>


<commit_before>var AD = require('AppDev');
var $ = require('jquery');

module.exports = $.Window('AppDev.UI.ViewContactWindow', {
    dependencies: ['AddContactWindow', 'DatePickerWindow', 'Checkbox'],

    contactMethods: [
        {label: 'contact_call', callback: 'callContact', field: 'contact_phone'},
        {label: 'contact_SMS', callback: 'SMSContact', field: 'contact_phone'},
        {label: 'contact_email', callback: 'emailContact', field: 'contact_email'}
    ],
    actions: [{
        title: 'del',
        callback: function() {
            AD.UI.yesNoAlert('contactDeleteConfirmation').done(this.proxy(function() {
                // The user chose "Yes", so close the window and delete the contact
                this.dfd.reject();
                this.contact.destroy();
            }));
        },
        platform: 'Android',
        showAsAction: true,
        icon: '/images/ic_action_discard.png'
    }, {
        title: 'edit',
        callback: function() {
            // Open the EditContact window
            var _this = this;
            var $winEditContact = this.createWindow('AddContactWindow', {
                operation: 'edit',
                existingContact: this.contact
            });
            $winEditContact.getDeferred().done(function() {
                // Update the steps view after the contact is edited
                _this.updateSteps();
            });
        },
        rightNavButton: true,
        showAsAction: true,
        icon: '/images/ic_action_edit.png'
    }]
}, {
    init: function(options) {
        this.contact = this.options.contact;

        // Initialize the base $.Window object
        this._super({
            title: 'viewContact',
            autoOpen: true,
            createParams: {
                layout: 'vertical'
            }
        });

        this.smartBind(this.contact, 'updated.attr', function(property, value) {
            // Re-initialize the name label and contact buttons
            this.initialize();

            // Simulate a global model 'updated' event
            $(this.contact.constructor).trigger('updated', this.contact);
        });
    },

    // Create the child views
    create: function() {
        var contact = this.contact;

        // Create a container to hold the label and optional image
        var headerView = this.add(Ti.UI.createView({
            left: AD.UI.padding,
            top: AD.UI.padding,
            width: AD.UI.useableScreenWidth,
            height: Ti.UI.SIZE
        }));

        // Show the contact's image if it exists
        var localContact = contact.contact_recordId === null ? null : Ti.Contacts.getPersonByID(contact.contact_recordId);
        var contactImage = localContact && localContact.getImage();
        var hasImage = contactImage ? true : false;
        if (hasImage) {
            var dimensions = AD.UI.getImageScaledDimensions(contactImage, AD.UI.contactImageSize);
            headerView.add(Ti.UI.createImageView({
                left: 0,
                width: dimensions.width,
                height: dimensions.height,
                image: contactImage
            }));
        }

        // Create the contact label
        headerView.add(this.record('nameLabel', Ti.UI.createLabel({
            left: hasImage ? AD.UI.contactImageSize.width : 0,
            top: 0,
            text: null,
            textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
            width: Ti.UI.FILL,
            font: AD.UI.Fonts.header
        })));

        // Create the contact button bar which allows the user to call, SMS, or e-mail the contact
        if (AD.Platform.isiOS) {
            // Create a button bar under iOS
            this.contactBBLabels = this.constructor.contactMethods.map(function(method) {
                return $.extend({
                    title: AD.Localize(method.label)
                }, method);
            });
            var contactBB = this.add('contactBB', Ti.UI.createButtonBar({
                left: AD.UI.padding,
                top: AD.UI.padding * 2,
                width: AD.UI.useableScreenWidth,
                height: AD.UI.buttonHeight,
                style: Ti.UI.iPhone.SystemButtonStyle.BAR,
                labels: this.contactBBLabels
            }));
            contactBB.addEventListener('click', this.proxy(function(event) {
                var callbackName = this.constructor.contactMethods[event.index].callback;
                // callbackName will be undefined if event.index is null
                // because the user clicked the button bar, but not a button
                if (typeof callbackName !== 'undefined') {
                    // Determine the proper callback to use and call it
                    var callback = $.isFunction(callback) ? callback : this[callbackName];
                    callback.call(this);
                }
            }));
        }
        else {
            // Create multiple buttons under any other platform

            // The buttons are spaced evenly and horizontally with AD.UI.padding units of padding between them
            var buttonCount = this.constructor.contactMethods.length;
            var buttonWidth = (AD.UI.useableScreenWidth - (buttonCount - 1) * AD.UI.padding) / buttonCount;
            var contactButtonView = this.add(Ti.UI.createView({
                left: AD.UI.padding,
                top: AD.UI.padding * 2,
                width: AD.UI.useableScreenWidth,
                height: AD.UI.buttonHeight
            }));
            this.constructor.contactMethods.forEach(function(method, index) {
                var buttonParams = $.extend({
                    left: (buttonWidth + AD.UI.padding) * index,
                    width: buttonWidth,
                    height: AD.UI.buttonHeight,
                    titleid: method.label
                }, method);
                var button = this.record(method.label, Ti.UI.createButton(buttonParams));
                contactButtonView.add(button);
                button.addEventListener('click', this.proxy(method.callback));
            }, this);
        }

        // Create the tags row
        var _this = this;
        var tagsRow = this.add('tags', this.createRow());
        tagsRow.top = AD.UI.padding;
        tagsRow.add(Ti.UI.createImageView({
            left: AD.UI.padding,
            width: Ti.UI.SIZE,
            height: Ti.UI.SIZE,
            image: '/images/tags.png'
        }));
        var tagsLabel = Ti.UI.createLabel({
            left: AD.UI.padding * 2 + 25,
            right: AD.UI.padding,
            top: 0,
            height: Ti.UI.FILL,
            font: AD.UI.Fonts.mediumSmall
        });
        var updateTagLabel = function() {
            // Get an array of the lab
            var tagLabels = contact.getTags().map(function(tag) { return tag.attr('tag_label'); });
            tagsLabel.text = (tagLabels.join(', ') || AD.Localize('none'));
        };
        updateTagLabel();
        tagsRow.add(tagsLabel);
        tagsRow.addEventListener('click', function() {
            _this.createWindow('ChooseOptionsWindow', {
                groupName: 'tag',
                Model: 'Tag',
                initial: contact.getTags().map(function(tag) { return tag.attr('tag_uuid'); }),
                editable: true
            }).getDeferred().done(function(options) {
                contact.setTags(options);
                updateTagLabel();
            });
        });

        // Create the campus steps view
        var scrollableStepsView = Ti.UI.createScrollView({
            left: 0,
            top: 0,
            layout: 'vertical',
            scrollType: 'vertical',
            contentHeight: 'auto',
            showVerticalScrollIndicator: true
        });
        this.add(scrollableStepsView);

        var $stepsView = this.record('steps', $.View.create(Ti.UI.createView({
            left: 0,
            top: 0,
            width: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            layout: 'vertical'
        })));
        scrollableStepsView.add($stepsView.getView());

        var personalStepsHeaderRow = Ti.UI.createView({
            left: 0,
            top: 0,
            width: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            backgroundColor: 'lightgray'
        });
        personalStepsHeaderRow.add(Ti.UI.createLabel({
            left: AD.UI.padding,
            top: AD.UI.padding,
            bottom: AD.UI.padding,
            width: Ti.UI.SIZE,
            height: Ti.UI.SIZE,
            textid: 'personalSteps'
        }));
        personalStepsHeaderRow.addEventListener('click', function() {
            _this.addPersonalSteps();
        });
        scrollableStepsView.add(personalStepsHeaderRow);

        // Create the personal steps view
        var $personalStepsView = this.record('personalSteps', $.View.create(Ti.UI.createView({
            left: 0,
            top: 0,
            width: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            layout: 'vertical'
        })));
        scrollableStepsView.add($personalStepsView.getView());
    },

    // Initialize the child views
    initialize: function() {
        this.getChild('nameLabel').text = this.contact.getLabel();

        this.updateSteps();

        // Update the enabled status of each of the contact buttons

        // Extract the button array
        var buttons = AD.Platform.isiOS ? this.contactBBLabels : this.constructor.contactMethods.map(function(method) {
            return this.getChild(method.label);
        }, this);
        buttons.forEach(function(button) {
            button.enabled = this.contact.attr(button.field) ? true : false;
        }, this);
        if (AD.Platform.isiOS) {
            // Force the button bar to recognize the new button states
            this.getChild('contactBB').labels = buttons;
        }
    },

    // Create and return a basic UI element representing a row
    createRow: function() {
        return Ti.UI.createView({
            left: 0,
            top: 0,
            height: AD.UI.buttonHeight,
            borderWidth: 1,
            borderColor: 'black'
        });
    },

    // (Re)create the steps UI
    updateSteps: function() {
        // Remove the steps view in order to replace it
        var $stepsView = this.get$Child('steps');
        var $personalStepsView = this.get$Child('personalSteps');

        $stepsView.removeAllChildren();
        $personalStepsView.removeAllChildren();

        // Display all the steps associated with this contact's campus
        // and all the completed steps, regardless of their campus
        var steps = this.contact.getSteps();
        var stepsLength = steps.length;
        steps.forEach(function(contactStep) {
            var $stepView = this.createStepRow(contactStep);
            if (contactStep.attr('campus_uuid')) {
                $stepsView.add($stepView);
            }
            else {
                $personalStepsView.add($stepView);
            }
        }, this);
    },

    createStepRow: function(contactStep) {
        // Lookup the associated ContactStep
        var step_uuid = contactStep.attr('step_uuid');
        var _this = this;
        var $newRow = $.View.create(this.createRow());

        // Create the step title
        var stepLabel = $newRow.add(Ti.UI.createLabel({
            left: AD.UI.padding,
            right: AD.UI.padding,
            top: 0,
            height: Ti.UI.FILL,
            text: contactStep.getLabel(),
            font: AD.UI.Fonts.mediumSmall
        }));

        var stepCompletedDate = contactStep.attr('step_date');
        var stepCompleted = stepCompletedDate !== null;

        // Create the switch to toggle the step's completion status
        var $completedCheckbox = new AD.UI.Checkbox({
            createParams: {
                right: AD.UI.padding
            },
            value: stepCompleted
        });
        var completedCheckbox = $completedCheckbox.getView();
        completedCheckbox.addEventListener('change', function(event) {
            // The step's completion state has been changed
            stepCompleted = event.value;
            stepCompletedDate = stepCompleted ? $.today() : null;
            contactStep.attr('step_date', stepCompletedDate).save();
            updateRow();
        });
        $newRow.add($completedCheckbox);

        // Create the button to set the step completion date
        var dateButton = Ti.UI.createButton({
            right: AD.UI.Checkbox.defaultSize + AD.UI.padding * 2,
            width: AD.Platform.isiOS ? 90 : 120,
            height: AD.UI.buttonHeight,
            title: ''
        });
        dateButton.addEventListener('click', function() {
            // Set the completion date of the step
            _this.createWindow('DatePickerWindow', {
                minDate: new Date(2012, 0, 1), // January 1, 2012
                maxDate: $.today(),
                initialDate: stepCompletedDate
            }).getDeferred().done(function(completedDate) {
                stepCompletedDate = completedDate;
                contactStep.attr('step_date', stepCompletedDate).save();
                updateRow();
            });
        });
        $newRow.add(dateButton);

        // Update the checkbox image and the title and visibility of the dateButton
        var updateRow = function() {
            if (stepCompleted) {
                dateButton.visible = true;
                dateButton.title = $.formatDate(stepCompletedDate);
            }
            else {
                dateButton.visible = false;
            }
            stepLabel.right = AD.UI.padding + (dateButton.visible ? (dateButton.right + dateButton.width) : (completedCheckbox.right + completedCheckbox.width));
        };
        updateRow();

        return $newRow;
    },

    // Helper functions that allow the user to contact the contact via telephone, SMS, or e-mail
    callContact: function() {
        // Remove all non-numeric numbers from the phone number
        var strippedPhoneNumber = this.contact.contact_phone.replace(/\D/g, '');
        Ti.Platform.openURL('tel:'+strippedPhoneNumber); // tel:xxxxxxxx format
        if (AD.Platform.isiOS) {
            Ti.App.iOS.registerBackgroundService({ url: 'Return.js' });
        }
    },
    SMSContact: function() {
        // Remove all non-numeric numbers from the phone number
        var strippedPhoneNumber = this.contact.contact_phone.replace(/\D/g, '');
        Ti.Platform.openURL('sms:'+strippedPhoneNumber); // sms:xxxxxxxx format
    },
    emailContact: function() {
        // Display the email dialog
        var emailDialog = Ti.UI.createEmailDialog({
            toRecipients: [this.contact.contact_email]
        });
        emailDialog.open();
    },

    // Display a window allowing the user to create personal steps and apply them to the current contact
    addPersonalSteps: function() {
        var contact = this.contact;
        var _this = this;
        this.createWindow('ChooseOptionsWindow', {
            groupName: 'personalStep',
            Model: 'Step',
            filter: { campus_uuid: null },
            initial: contact.getPersonalSteps().map(function(step) { return step.attr('step_uuid'); }),
            editable: true
        }).getDeferred().done(function(options) {
            contact.setPersonalSteps(options);
            _this.updateSteps();
        });
    }
});
<commit_msg>Rename variables to clarify whether steps are campus steps or personal steps<commit_after>var AD = require('AppDev');
var $ = require('jquery');

module.exports = $.Window('AppDev.UI.ViewContactWindow', {
    dependencies: ['AddContactWindow', 'DatePickerWindow', 'Checkbox'],

    contactMethods: [
        {label: 'contact_call', callback: 'callContact', field: 'contact_phone'},
        {label: 'contact_SMS', callback: 'SMSContact', field: 'contact_phone'},
        {label: 'contact_email', callback: 'emailContact', field: 'contact_email'}
    ],
    actions: [{
        title: 'del',
        callback: function() {
            AD.UI.yesNoAlert('contactDeleteConfirmation').done(this.proxy(function() {
                // The user chose "Yes", so close the window and delete the contact
                this.dfd.reject();
                this.contact.destroy();
            }));
        },
        platform: 'Android',
        showAsAction: true,
        icon: '/images/ic_action_discard.png'
    }, {
        title: 'edit',
        callback: function() {
            // Open the EditContact window
            var _this = this;
            var $winEditContact = this.createWindow('AddContactWindow', {
                operation: 'edit',
                existingContact: this.contact
            });
            $winEditContact.getDeferred().done(function() {
                // Update the steps view after the contact is edited
                _this.updateSteps();
            });
        },
        rightNavButton: true,
        showAsAction: true,
        icon: '/images/ic_action_edit.png'
    }]
}, {
    init: function(options) {
        this.contact = this.options.contact;

        // Initialize the base $.Window object
        this._super({
            title: 'viewContact',
            autoOpen: true,
            createParams: {
                layout: 'vertical'
            }
        });

        this.smartBind(this.contact, 'updated.attr', function(property, value) {
            // Re-initialize the name label and contact buttons
            this.initialize();

            // Simulate a global model 'updated' event
            $(this.contact.constructor).trigger('updated', this.contact);
        });
    },

    // Create the child views
    create: function() {
        var contact = this.contact;

        // Create a container to hold the label and optional image
        var headerView = this.add(Ti.UI.createView({
            left: AD.UI.padding,
            top: AD.UI.padding,
            width: AD.UI.useableScreenWidth,
            height: Ti.UI.SIZE
        }));

        // Show the contact's image if it exists
        var localContact = contact.contact_recordId === null ? null : Ti.Contacts.getPersonByID(contact.contact_recordId);
        var contactImage = localContact && localContact.getImage();
        var hasImage = contactImage ? true : false;
        if (hasImage) {
            var dimensions = AD.UI.getImageScaledDimensions(contactImage, AD.UI.contactImageSize);
            headerView.add(Ti.UI.createImageView({
                left: 0,
                width: dimensions.width,
                height: dimensions.height,
                image: contactImage
            }));
        }

        // Create the contact label
        headerView.add(this.record('nameLabel', Ti.UI.createLabel({
            left: hasImage ? AD.UI.contactImageSize.width : 0,
            top: 0,
            text: null,
            textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
            width: Ti.UI.FILL,
            font: AD.UI.Fonts.header
        })));

        // Create the contact button bar which allows the user to call, SMS, or e-mail the contact
        if (AD.Platform.isiOS) {
            // Create a button bar under iOS
            this.contactBBLabels = this.constructor.contactMethods.map(function(method) {
                return $.extend({
                    title: AD.Localize(method.label)
                }, method);
            });
            var contactBB = this.add('contactBB', Ti.UI.createButtonBar({
                left: AD.UI.padding,
                top: AD.UI.padding * 2,
                width: AD.UI.useableScreenWidth,
                height: AD.UI.buttonHeight,
                style: Ti.UI.iPhone.SystemButtonStyle.BAR,
                labels: this.contactBBLabels
            }));
            contactBB.addEventListener('click', this.proxy(function(event) {
                var callbackName = this.constructor.contactMethods[event.index].callback;
                // callbackName will be undefined if event.index is null
                // because the user clicked the button bar, but not a button
                if (typeof callbackName !== 'undefined') {
                    // Determine the proper callback to use and call it
                    var callback = $.isFunction(callback) ? callback : this[callbackName];
                    callback.call(this);
                }
            }));
        }
        else {
            // Create multiple buttons under any other platform

            // The buttons are spaced evenly and horizontally with AD.UI.padding units of padding between them
            var buttonCount = this.constructor.contactMethods.length;
            var buttonWidth = (AD.UI.useableScreenWidth - (buttonCount - 1) * AD.UI.padding) / buttonCount;
            var contactButtonView = this.add(Ti.UI.createView({
                left: AD.UI.padding,
                top: AD.UI.padding * 2,
                width: AD.UI.useableScreenWidth,
                height: AD.UI.buttonHeight
            }));
            this.constructor.contactMethods.forEach(function(method, index) {
                var buttonParams = $.extend({
                    left: (buttonWidth + AD.UI.padding) * index,
                    width: buttonWidth,
                    height: AD.UI.buttonHeight,
                    titleid: method.label
                }, method);
                var button = this.record(method.label, Ti.UI.createButton(buttonParams));
                contactButtonView.add(button);
                button.addEventListener('click', this.proxy(method.callback));
            }, this);
        }

        // Create the tags row
        var _this = this;
        var tagsRow = this.add('tags', this.createRow());
        tagsRow.top = AD.UI.padding;
        tagsRow.add(Ti.UI.createImageView({
            left: AD.UI.padding,
            width: Ti.UI.SIZE,
            height: Ti.UI.SIZE,
            image: '/images/tags.png'
        }));
        var tagsLabel = Ti.UI.createLabel({
            left: AD.UI.padding * 2 + 25,
            right: AD.UI.padding,
            top: 0,
            height: Ti.UI.FILL,
            font: AD.UI.Fonts.mediumSmall
        });
        var updateTagLabel = function() {
            // Get an array of the lab
            var tagLabels = contact.getTags().map(function(tag) { return tag.attr('tag_label'); });
            tagsLabel.text = (tagLabels.join(', ') || AD.Localize('none'));
        };
        updateTagLabel();
        tagsRow.add(tagsLabel);
        tagsRow.addEventListener('click', function() {
            _this.createWindow('ChooseOptionsWindow', {
                groupName: 'tag',
                Model: 'Tag',
                initial: contact.getTags().map(function(tag) { return tag.attr('tag_uuid'); }),
                editable: true
            }).getDeferred().done(function(options) {
                contact.setTags(options);
                updateTagLabel();
            });
        });

        // Create the campus steps view
        var scrollableStepsView = Ti.UI.createScrollView({
            left: 0,
            top: 0,
            layout: 'vertical',
            scrollType: 'vertical',
            contentHeight: 'auto',
            showVerticalScrollIndicator: true
        });
        this.add(scrollableStepsView);

        var $campusStepsView = this.record('campusSteps', $.View.create(Ti.UI.createView({
            left: 0,
            top: 0,
            width: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            layout: 'vertical'
        })));
        scrollableStepsView.add($campusStepsView.getView());

        var personalStepsHeaderRow = Ti.UI.createView({
            left: 0,
            top: 0,
            width: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            backgroundColor: 'lightgray'
        });
        personalStepsHeaderRow.add(Ti.UI.createLabel({
            left: AD.UI.padding,
            top: AD.UI.padding,
            bottom: AD.UI.padding,
            width: Ti.UI.SIZE,
            height: Ti.UI.SIZE,
            textid: 'personalSteps'
        }));
        personalStepsHeaderRow.addEventListener('click', function() {
            _this.addPersonalSteps();
        });
        scrollableStepsView.add(personalStepsHeaderRow);

        // Create the personal steps view
        var $personalStepsView = this.record('personalSteps', $.View.create(Ti.UI.createView({
            left: 0,
            top: 0,
            width: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            layout: 'vertical'
        })));
        scrollableStepsView.add($personalStepsView.getView());
    },

    // Initialize the child views
    initialize: function() {
        this.getChild('nameLabel').text = this.contact.getLabel();

        this.updateSteps();

        // Update the enabled status of each of the contact buttons

        // Extract the button array
        var buttons = AD.Platform.isiOS ? this.contactBBLabels : this.constructor.contactMethods.map(function(method) {
            return this.getChild(method.label);
        }, this);
        buttons.forEach(function(button) {
            button.enabled = this.contact.attr(button.field) ? true : false;
        }, this);
        if (AD.Platform.isiOS) {
            // Force the button bar to recognize the new button states
            this.getChild('contactBB').labels = buttons;
        }
    },

    // Create and return a basic UI element representing a row
    createRow: function() {
        return Ti.UI.createView({
            left: 0,
            top: 0,
            height: AD.UI.buttonHeight,
            borderWidth: 1,
            borderColor: 'black'
        });
    },

    // (Re)create the steps UI
    updateSteps: function() {
        // Remove the steps view in order to replace it
        var $campusStepsView = this.get$Child('campusSteps');
        var $personalStepsView = this.get$Child('personalSteps');

        $campusStepsView.removeAllChildren();
        $personalStepsView.removeAllChildren();

        // Display all the steps associated with this contact's campus
        // and all the completed steps, regardless of their campus
        var steps = this.contact.getSteps();
        var stepsLength = steps.length;
        steps.forEach(function(contactStep) {
            var $stepView = this.createStepRow(contactStep);
            if (contactStep.attr('campus_uuid')) {
                $campusStepsView.add($stepView);
            }
            else {
                $personalStepsView.add($stepView);
            }
        }, this);
    },

    createStepRow: function(contactStep) {
        // Lookup the associated ContactStep
        var step_uuid = contactStep.attr('step_uuid');
        var _this = this;
        var $newRow = $.View.create(this.createRow());

        // Create the step title
        var stepLabel = $newRow.add(Ti.UI.createLabel({
            left: AD.UI.padding,
            right: AD.UI.padding,
            top: 0,
            height: Ti.UI.FILL,
            text: contactStep.getLabel(),
            font: AD.UI.Fonts.mediumSmall
        }));

        var stepCompletedDate = contactStep.attr('step_date');
        var stepCompleted = stepCompletedDate !== null;

        // Create the switch to toggle the step's completion status
        var $completedCheckbox = new AD.UI.Checkbox({
            createParams: {
                right: AD.UI.padding
            },
            value: stepCompleted
        });
        var completedCheckbox = $completedCheckbox.getView();
        completedCheckbox.addEventListener('change', function(event) {
            // The step's completion state has been changed
            stepCompleted = event.value;
            stepCompletedDate = stepCompleted ? $.today() : null;
            contactStep.attr('step_date', stepCompletedDate).save();
            updateRow();
        });
        $newRow.add($completedCheckbox);

        // Create the button to set the step completion date
        var dateButton = Ti.UI.createButton({
            right: AD.UI.Checkbox.defaultSize + AD.UI.padding * 2,
            width: AD.Platform.isiOS ? 90 : 120,
            height: AD.UI.buttonHeight,
            title: ''
        });
        dateButton.addEventListener('click', function() {
            // Set the completion date of the step
            _this.createWindow('DatePickerWindow', {
                minDate: new Date(2012, 0, 1), // January 1, 2012
                maxDate: $.today(),
                initialDate: stepCompletedDate
            }).getDeferred().done(function(completedDate) {
                stepCompletedDate = completedDate;
                contactStep.attr('step_date', stepCompletedDate).save();
                updateRow();
            });
        });
        $newRow.add(dateButton);

        // Update the checkbox image and the title and visibility of the dateButton
        var updateRow = function() {
            if (stepCompleted) {
                dateButton.visible = true;
                dateButton.title = $.formatDate(stepCompletedDate);
            }
            else {
                dateButton.visible = false;
            }
            stepLabel.right = AD.UI.padding + (dateButton.visible ? (dateButton.right + dateButton.width) : (completedCheckbox.right + completedCheckbox.width));
        };
        updateRow();

        return $newRow;
    },

    // Helper functions that allow the user to contact the contact via telephone, SMS, or e-mail
    callContact: function() {
        // Remove all non-numeric numbers from the phone number
        var strippedPhoneNumber = this.contact.contact_phone.replace(/\D/g, '');
        Ti.Platform.openURL('tel:'+strippedPhoneNumber); // tel:xxxxxxxx format
        if (AD.Platform.isiOS) {
            Ti.App.iOS.registerBackgroundService({ url: 'Return.js' });
        }
    },
    SMSContact: function() {
        // Remove all non-numeric numbers from the phone number
        var strippedPhoneNumber = this.contact.contact_phone.replace(/\D/g, '');
        Ti.Platform.openURL('sms:'+strippedPhoneNumber); // sms:xxxxxxxx format
    },
    emailContact: function() {
        // Display the email dialog
        var emailDialog = Ti.UI.createEmailDialog({
            toRecipients: [this.contact.contact_email]
        });
        emailDialog.open();
    },

    // Display a window allowing the user to create personal steps and apply them to the current contact
    addPersonalSteps: function() {
        var contact = this.contact;
        var _this = this;
        this.createWindow('ChooseOptionsWindow', {
            groupName: 'personalStep',
            Model: 'Step',
            filter: { campus_uuid: null },
            initial: contact.getPersonalSteps().map(function(step) { return step.attr('step_uuid'); }),
            editable: true
        }).getDeferred().done(function(options) {
            contact.setPersonalSteps(options);
            _this.updateSteps();
        });
    }
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import Relay from 'react-relay';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import TextField from 'material-ui/TextField';
import styled from 'styled-components';
import rtlDetect from 'rtl-detect';
import MediaStatus from './MediaStatus';
import MediaTags from './MediaTags';
import MediaActions from './MediaActions';
import MediaUtil from './MediaUtil';
import TimeBefore from '../TimeBefore';
import UpdateProjectMediaMutation from '../../relay/UpdateProjectMediaMutation';
import CheckContext from '../../CheckContext';
import ProfileLink from '../layout/ProfileLink';
import Message from '../Message';
import {
  FlexRow,
  black87,
  title,
  units,
} from '../../styles/js/variables';

const StyledMetadata = styled(FlexRow)`
  margin-top: ${units(3)};

  > * {
    padding: 0 ${units(1)};
  }

  .media-detail__check-added-at,
  .media-detail__check-added-by {
    align-items: center;
    display: flex;
    flex-shrink: 0;
  }

  // Move dialog
  //
  .media-detail__dialog-header {
    color: ${black87};
    font: ${title};
    height: ${units(4)};
    margin-bottom: ${units(0.5)};
    margin-top: ${units(0.5)};
    margin-${props => props.fromDirection}: auto;
  }

  .media-detail__dialog-media-path {
    height: ${units(2)};
    margin-bottom: ${units(4)};
    text-align: ${props => props.fromDirection};
  }

  .media-detail__dialog-radio-group {
    margin-top: ${units(4)};
    margin-${props => props.fromDirection}: ${units(4)};
  }
`;

const messages = defineMessages({
  mediaTitle: {
    id: 'mediaDetail.mediaTitle',
    defaultMessage: 'Title',
  },
  error: {
    id: 'mediaDetail.moveFailed',
    defaultMessage: 'Sorry, we could not move this report',
  },
});

class MediaMetadata extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isEditing: false,
      openMoveDialog: false,
      mediaVersion: false,
      openEditDialog: false,
    };
  }

  getContext() {
    const context = new CheckContext(this).getContextStore();
    return context;
  }

  handleError(json) {
    let message = `${this.props.intl.formatMessage(messages.error)}`;
    if (json && json.error) {
      message = json.error;
    }
    this.setState({ message });
  }

  handleRefresh() {
    const onFailure = (transaction) => {
      const transactionError = transaction.getError();
      transactionError.json
        ? transactionError.json().then(this.handleError)
        : this.handleError(JSON.stringify(transactionError));
    };

    const onSuccess = (response) => {
      this.setState({
        mediaVersion: JSON.parse(response.updateProjectMedia.project_media.embed).refreshes_count,
      });
    };

    Relay.Store.commitUpdate(
      new UpdateProjectMediaMutation({
        refresh_media: 1,
        id: this.props.media.id,
      }),
      { onSuccess, onFailure },
    );
  }

  handleEdit() {
    this.setState({ isEditing: true });
  }

  handleMove() {
    this.setState({ openMoveDialog: true });
  }

  handleMoveProjectMedia() {
    const { media } = this.props;
    const projectId = this.state.dstProj.dbid;
    const previousProjectId = this.currentProject().node.dbid;
    const history = this.getContext().history;

    const onFailure = (transaction) => {
      if (/^\/[^/]+\/project\/[0-9]+$/.test(window.location.pathname)) {
        history.push(`/${media.team.slug}/project/${previousProjectId}`);
      }
      const transactionError = transaction.getError();
      transactionError.json
        ? transactionError.json().then(this.handleError)
        : this.handleError(JSON.stringify(transactionError));
    };

    const path = `/${media.team.slug}/project/${projectId}`;

    const onSuccess = () => {
      if (/^\/[^/]+\/search\//.test(window.location.pathname)) {
        this.props.parentComponent.props.relay.forceFetch();
      } else if (/^\/[^/]+\/project\/[0-9]+\/media\/[0-9]+$/.test(window.location.pathname)) {
        history.push(`${path}/media/${media.dbid}`);
      }
    };

    // Optimistic-redirect to target project
    if (/^\/[^/]+\/project\/[0-9]+$/.test(window.location.pathname)) {
      history.push(path);
    }

    Relay.Store.commitUpdate(
      new UpdateProjectMediaMutation({
        project_id: projectId,
        id: media.id,
        srcProj: this.currentProject().node,
        dstProj: this.state.dstProj,
      }),
      { onSuccess, onFailure },
    );

    this.setState({ openMoveDialog: false });
  }

  currentProject() {
    const projectId = this.props.media.project_id;
    const context = this.getContext();
    const projects = context.team.projects.edges;

    return projects[projects.findIndex(p => p.node.dbid === projectId)];
  }

  destinationProjects() {
    const projectId = this.props.media.project_id;
    const context = this.getContext();
    const projects = context.team.projects.edges.sortp((a, b) =>
      a.node.title.localeCompare(b.node.title),
    );

    return projects.filter(p => p.node.dbid !== projectId);
  }

  handleSave(media, event) {
    if (event) {
      event.preventDefault();
    }

    const titleInput = document.querySelector(`#media-detail-title-input-${media.dbid}`);
    const newTitle = (titleInput.value || '').trim();

    const onFailure = (transaction) => {
      const transactionError = transaction.getError();
      transactionError.json
        ? transactionError.json().then(alert('Unhandled error'))
        : alert(JSON.stringify(transactionError));
    };

    const onSuccess = () => {
      this.setState({ isEditing: false });
    };

    Relay.Store.commitUpdate(
      new UpdateProjectMediaMutation({
        embed: JSON.stringify({ title: newTitle }),
        id: media.id,
      }),
      { onSuccess, onFailure },
    );

    this.setState({ isEditing: false });
  }

  handleCancel() {
    this.setState({ isEditing: false });
  }

  handleCloseDialogs() {
    this.setState({ isEditing: false, openMoveDialog: false, dstProj: null });
  }

  handleSelectDestProject(event, dstProj) {
    this.setState({ dstProj });
  }

  render() {
    const { media, readonly, mediaUrl } = this.props;
    const createdAt = MediaUtil.createdAt(media);
    const context = this.getContext();
    const currentProject = this.currentProject();
    const destinationProjects = this.destinationProjects();
    const locale = this.props.intl.locale;
    const isRtl = rtlDetect.isRtlLang(locale);
    const fromDirection = isRtl ? 'right' : 'left';

    const byUser = media.user &&
      media.user.source &&
      media.user.source.dbid &&
      media.user.name !== 'Pender'
      ? (<FormattedMessage
        id="mediaDetail.byUser"
        defaultMessage={'by {username}'}
        values={{ username: <ProfileLink user={media.user} /> }}
      />)
      : '';
    const moveDialogActions = [
      <FlatButton
        label={<FormattedMessage id="mediaDetail.cancelButton" defaultMessage="Cancel" />}
        primary
        onClick={this.handleCloseDialogs.bind(this)}
      />,
      <FlatButton
        label={<FormattedMessage id="mediaDetail.move" defaultMessage="Move" />}
        primary
        keyboardFocused
        onClick={this.handleMoveProjectMedia.bind(this)}
        disabled={!this.state.dstProj}
      />,
    ];

    const editDialog = (<Dialog
      modal
      open={this.state.isEditing}
      onRequestClose={this.handleCloseDialogs.bind(this)}
      autoScrollBodyContent
    >
      <form onSubmit={this.handleSave.bind(this, media)}>
        <Message message={this.state.message} />
        <TextField
          type="text"
          id={`media-detail-title-input-${media.dbid}`}
          className="media-detail__title-input"
          placeholder={this.props.intl.formatMessage(messages.mediaTitle)}
          defaultValue={this.props.heading}
          style={{ width: '100%' }}
        />
      </form>

      {media.tags ? <MediaTags media={media} tags={media.tags.edges} isEditing /> : null}

        <FlatButton
          onClick={this.handleCancel.bind(this)}
          className="media-detail__cancel-edits"
          label={<FormattedMessage id="mediaDetail.cancelButton" defaultMessage="Cancel" />}
        />
        <FlatButton
          onClick={this.handleSave.bind(this, media)}
          className="media-detail__save-edits"
          label={<FormattedMessage id="mediaDetail.doneButton" defaultMessage="Done" />}
        />

    </Dialog>);

    return (

    );
  }
}

MediaMetadata.contextTypes = {
  store: React.PropTypes.object,
};

export default injectIntl(MediaMetadata);
<commit_msg>#6168 Ensure media metadata wraps<commit_after>import React, { Component } from 'react';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import Relay from 'react-relay';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import TextField from 'material-ui/TextField';
import styled from 'styled-components';
import rtlDetect from 'rtl-detect';
import MediaStatus from './MediaStatus';
import MediaTags from './MediaTags';
import MediaActions from './MediaActions';
import MediaUtil from './MediaUtil';
import TimeBefore from '../TimeBefore';
import UpdateProjectMediaMutation from '../../relay/UpdateProjectMediaMutation';
import CheckContext from '../../CheckContext';
import ProfileLink from '../layout/ProfileLink';
import Message from '../Message';
import {
  FlexRow,
  black87,
  title,
  units,
} from '../../styles/js/variables';

const StyledMetadata = styled(FlexRow)`
  margin-top: ${units(3)};
  flex-wrap: wrap;

  > * {
    padding: 0 ${units(1)};
  }

  .media-detail__check-added-at,
  .media-detail__check-added-by {
    align-items: center;
    display: flex;
    flex-shrink: 0;
  }

  // Move dialog
  //
  .media-detail__dialog-header {
    color: ${black87};
    font: ${title};
    height: ${units(4)};
    margin-bottom: ${units(0.5)};
    margin-top: ${units(0.5)};
    margin-${props => props.fromDirection}: auto;
  }

  .media-detail__dialog-media-path {
    height: ${units(2)};
    margin-bottom: ${units(4)};
    text-align: ${props => props.fromDirection};
  }

  .media-detail__dialog-radio-group {
    margin-top: ${units(4)};
    margin-${props => props.fromDirection}: ${units(4)};
  }
`;

const messages = defineMessages({
  mediaTitle: {
    id: 'mediaDetail.mediaTitle',
    defaultMessage: 'Title',
  },
  error: {
    id: 'mediaDetail.moveFailed',
    defaultMessage: 'Sorry, we could not move this report',
  },
});

class MediaMetadata extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isEditing: false,
      openMoveDialog: false,
      mediaVersion: false,
      openEditDialog: false,
    };
  }

  getContext() {
    const context = new CheckContext(this).getContextStore();
    return context;
  }

  handleError(json) {
    let message = `${this.props.intl.formatMessage(messages.error)}`;
    if (json && json.error) {
      message = json.error;
    }
    this.setState({ message });
  }

  handleRefresh() {
    const onFailure = (transaction) => {
      const transactionError = transaction.getError();
      transactionError.json
        ? transactionError.json().then(this.handleError)
        : this.handleError(JSON.stringify(transactionError));
    };

    const onSuccess = (response) => {
      this.setState({
        mediaVersion: JSON.parse(response.updateProjectMedia.project_media.embed).refreshes_count,
      });
    };

    Relay.Store.commitUpdate(
      new UpdateProjectMediaMutation({
        refresh_media: 1,
        id: this.props.media.id,
      }),
      { onSuccess, onFailure },
    );
  }

  handleEdit() {
    this.setState({ isEditing: true });
  }

  handleMove() {
    this.setState({ openMoveDialog: true });
  }

  handleMoveProjectMedia() {
    const { media } = this.props;
    const projectId = this.state.dstProj.dbid;
    const previousProjectId = this.currentProject().node.dbid;
    const history = this.getContext().history;

    const onFailure = (transaction) => {
      if (/^\/[^/]+\/project\/[0-9]+$/.test(window.location.pathname)) {
        history.push(`/${media.team.slug}/project/${previousProjectId}`);
      }
      const transactionError = transaction.getError();
      transactionError.json
        ? transactionError.json().then(this.handleError)
        : this.handleError(JSON.stringify(transactionError));
    };

    const path = `/${media.team.slug}/project/${projectId}`;

    const onSuccess = () => {
      if (/^\/[^/]+\/search\//.test(window.location.pathname)) {
        this.props.parentComponent.props.relay.forceFetch();
      } else if (/^\/[^/]+\/project\/[0-9]+\/media\/[0-9]+$/.test(window.location.pathname)) {
        history.push(`${path}/media/${media.dbid}`);
      }
    };

    // Optimistic-redirect to target project
    if (/^\/[^/]+\/project\/[0-9]+$/.test(window.location.pathname)) {
      history.push(path);
    }

    Relay.Store.commitUpdate(
      new UpdateProjectMediaMutation({
        project_id: projectId,
        id: media.id,
        srcProj: this.currentProject().node,
        dstProj: this.state.dstProj,
      }),
      { onSuccess, onFailure },
    );

    this.setState({ openMoveDialog: false });
  }

  currentProject() {
    const projectId = this.props.media.project_id;
    const context = this.getContext();
    const projects = context.team.projects.edges;

    return projects[projects.findIndex(p => p.node.dbid === projectId)];
  }

  destinationProjects() {
    const projectId = this.props.media.project_id;
    const context = this.getContext();
    const projects = context.team.projects.edges.sortp((a, b) =>
      a.node.title.localeCompare(b.node.title),
    );

    return projects.filter(p => p.node.dbid !== projectId);
  }

  handleSave(media, event) {
    if (event) {
      event.preventDefault();
    }

    const titleInput = document.querySelector(`#media-detail-title-input-${media.dbid}`);
    const newTitle = (titleInput.value || '').trim();

    const onFailure = (transaction) => {
      const transactionError = transaction.getError();
      transactionError.json
        ? transactionError.json().then(alert('Unhandled error'))
        : alert(JSON.stringify(transactionError));
    };

    const onSuccess = () => {
      this.setState({ isEditing: false });
    };

    Relay.Store.commitUpdate(
      new UpdateProjectMediaMutation({
        embed: JSON.stringify({ title: newTitle }),
        id: media.id,
      }),
      { onSuccess, onFailure },
    );

    this.setState({ isEditing: false });
  }

  handleCancel() {
    this.setState({ isEditing: false });
  }

  handleCloseDialogs() {
    this.setState({ isEditing: false, openMoveDialog: false, dstProj: null });
  }

  handleSelectDestProject(event, dstProj) {
    this.setState({ dstProj });
  }

  render() {
    const { media, readonly, mediaUrl } = this.props;
    const createdAt = MediaUtil.createdAt(media);
    const context = this.getContext();
    const currentProject = this.currentProject();
    const destinationProjects = this.destinationProjects();
    const locale = this.props.intl.locale;
    const isRtl = rtlDetect.isRtlLang(locale);
    const fromDirection = isRtl ? 'right' : 'left';

    const byUser = media.user &&
      media.user.source &&
      media.user.source.dbid &&
      media.user.name !== 'Pender'
      ? (<FormattedMessage
        id="mediaDetail.byUser"
        defaultMessage={'by {username}'}
        values={{ username: <ProfileLink user={media.user} /> }}
      />)
      : '';
    const moveDialogActions = [
      <FlatButton
        label={<FormattedMessage id="mediaDetail.cancelButton" defaultMessage="Cancel" />}
        primary
        onClick={this.handleCloseDialogs.bind(this)}
      />,
      <FlatButton
        label={<FormattedMessage id="mediaDetail.move" defaultMessage="Move" />}
        primary
        keyboardFocused
        onClick={this.handleMoveProjectMedia.bind(this)}
        disabled={!this.state.dstProj}
      />,
    ];

    const editDialog = (<Dialog
      modal
      open={this.state.isEditing}
      onRequestClose={this.handleCloseDialogs.bind(this)}
      autoScrollBodyContent
    >
      <form onSubmit={this.handleSave.bind(this, media)}>
        <Message message={this.state.message} />
        <TextField
          type="text"
          id={`media-detail-title-input-${media.dbid}`}
          className="media-detail__title-input"
          placeholder={this.props.intl.formatMessage(messages.mediaTitle)}
          defaultValue={this.props.heading}
          style={{ width: '100%' }}
        />
      </form>

      {media.tags ? <MediaTags media={media} tags={media.tags.edges} isEditing /> : null}

        <FlatButton
          onClick={this.handleCancel.bind(this)}
          className="media-detail__cancel-edits"
          label={<FormattedMessage id="mediaDetail.cancelButton" defaultMessage="Cancel" />}
        />
        <FlatButton
          onClick={this.handleSave.bind(this, media)}
          className="media-detail__save-edits"
          label={<FormattedMessage id="mediaDetail.doneButton" defaultMessage="Done" />}
        />

    </Dialog>);

    return (

    );
  }
}

MediaMetadata.contextTypes = {
  store: React.PropTypes.object,
};

export default injectIntl(MediaMetadata);
<|endoftext|>