<commit_before>import moment from 'moment';
import Ember from 'ember';
import DS from 'ember-data';
import momentFormat from 'ember-moment/computeds/format';

const { Component, computed, isPresent, RSVP, inject } = Ember;
const { service } = inject;
const { all, Promise } = RSVP;
const { PromiseArray } = DS;

export default Component.extend({
  init() {
    this._super(...arguments);
    //do these on setup otherwise tests were failing because
    //the old filter value hung around
    this.set('selectedSessionTypes', []);
    this.set('selectedCourseLevels', []);
    this.set('selectedCohorts', []);
    this.set('selectedCourses', []);
  },
  userEvents: service(),
  schoolEvents: service(),
  currentUser: service(),
  store: service(),
  i18n: service(),
  activeFilters: null,
  selectedDate: null,
  selectedView: null,
  selectedCohorts: [],
  selectedCourseLevels: [],
  selectedCourses: [],
  selectedSessionTypes: [],

  dueTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.dueThisDay');
  }),
  dayTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.day');
  }),
  weekTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.week');
  }),
  monthTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.month');
  }),
  loadingEventsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.loadingEvents');
  }),
  icsInstructionsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.icsInstructions');
  }),
  fromTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).startOf(this.get('selectedView')).subtract(this.get('skew'), 'days').unix();
  }),
  toTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).endOf(this.get('selectedView')).add(this.get('skew'), 'days').unix();
  }),
  clockSkew: computed('selectedView', function(){
    if(this.set('selectedView') === 'month'){
      return 6;
    }

    return 1;
  }),
  calendarDate: momentFormat('selectedDate', 'YYYY-MM-DD'),

  /**
   * @property courseLevels
   * @type {Ember.computed}
   * @public
   * @todo Review whether this needs to be a computed property, get rid of it if applicable. [ST 2017/02/07]
   */
  courseLevels: computed('selectedCourseLevels.[]', function(){
    let levels = [];
    for(let i =1; i <=5; i++){
      levels.pushObject(i);
    }

    return levels;
  }),

  /**
   * @property courses
   * @type {Ember.computed}
   * @public
   */
  courses: computed('allCourses.[]', 'selectedCourses.[]', function(){
    return this.get('allCourses');
  }),

  /**
   * @property filteredEvents
   * @type {Ember.computed}
   * @public
   */
  filteredEvents: computed(
    'ourEvents.[]',
    'eventsWithSelectedSessionTypes.[]',
    'eventsWithSelectedCourseLevels.[]',
    'eventsWithSelectedCohorts.[]',
    'eventsWithSelectedCourses.[]',
    function() {
      return new Promise(resolve => {
        let promises = [];
        let eventTypes = [
          'eventsWithSelectedSessionTypes',
          'eventsWithSelectedCourseLevels',
          'eventsWithSelectedCohorts',
          'eventsWithSelectedCourses',
        ];
        let allFilteredEvents = [];
        eventTypes.forEach(name => {
          promises.pushObject(this.get(name).then(events => {
            allFilteredEvents.pushObject(events);
          }));
        });

        all(promises).then(()=> {
          this.get('ourEvents').then(events => {
            let filteredEvents = events.filter(event => {
              return allFilteredEvents.every(arr => {
                return arr.includes(event);
              });
            });
            resolve(filteredEvents);
          });
        });
      });
    }
  ),

  /**
   * @property sessionTypes
   * @type {Ember.computed}
   * @public
   */
  sessionTypes: computed('selectedSchool.sessionTypes.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then(school => {
        school.get('sessionTypes').then(types => {
          resolve(types.sortBy('title'));
        });
      });
    });
  }),

  /**
   * @property ourEvents
   * @type {Ember.computed}
   * @protected
   */
  ourEvents: computed('mySchedule', 'fromTimeStamp', 'toTimeStamp', 'selectedSchool', 'selectedView', function(){
    return new Promise(resolve => {
      if(this.get('mySchedule')) {
        this.get('userEvents').getEvents(this.get('fromTimeStamp'), this.get('toTimeStamp')).then(userEvents => {
          resolve(userEvents);
        });
      } else {
        this.get('selectedSchool').then(school => {
          this.get('schoolEvents').getEvents(
            school.get('id'),
            this.get('fromTimeStamp'),
            this.get('toTimeStamp')
          ).then(schoolEvents => {
            resolve(schoolEvents);
          });
        });
      }
    });
  }),

  /**
   * @property eventsWithSelectedSessionTypes
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedSessionTypes: computed('ourEvents.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedSessionTypes = this.get('selectedSessionTypes').mapBy('id');
        if(selectedSessionTypes.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getSessionTypeIdForEvent(event).then(id => {
              if (selectedSessionTypes.includes(id)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourseLevels
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourseLevels: computed('ourEvents.[]', 'selectedCourseLevels.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCourseLevels = this.get('selectedCourseLevels');
        if(selectedCourseLevels.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseLevelForEvent(event).then(level => {
              if (selectedCourseLevels.includes(level)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCohorts
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCohorts: computed('ourEvents.[]', 'selectedCohorts.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCohorts = this.get('selectedCohorts').mapBy('id');
        if(selectedCohorts.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCohortIdsForEvent(event).then(cohorts => {
              if (cohorts.any(cohortId => {
                return selectedCohorts.includes(cohortId);
              })) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourses
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourses: computed('ourEvents.[]', 'selectedCourses.[]', function(){
    return new Promise(resolve => {
      let selectedCourses = this.get('selectedCourses').mapBy('id');
      this.get('ourEvents').then(events => {
        if(selectedCourses.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseIdForEvent(event).then(courseId => {
              if (selectedCourses.includes(courseId)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  allCohorts: computed('selectedSchool', 'selectedAcademicYear', function(){
    let defer = defer();
    this.get('selectedSchool').then(school => {
      this.get('selectedAcademicYear').then(year => {
        school.getCohortsForYear(year.get('title')).then(cohorts => {
          defer.resolve(cohorts.sortBy('displayTitle'));
        });
      });
    });
    return PromiseArray.create({
      promise: defer.promise
    });
  }),
  cohorts: computed('allCohorts.[].displayTitle', 'selectedCohorts.[]', function(){
    return this.get('allCohorts');
  }),

  /**
   * @property allCourses
   * @type {Ember.computed}
   * @protected
   */
  allCourses: computed('selectedSchool', 'selectedAcademicYear', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then((school) => {
        this.get('selectedAcademicYear').then((year) => {
          this.get('store').query('course', {
            filters: {
              school: school.get('id'),
              year: year.get('title')
            },
            limit: 1000
          }).then((courses) => {
            resolve(courses.sortBy('title'));
          });
        });
      });
    });
  }),

  selectedSchool: computed('schoolPickedByUser', 'currentUser.model.school', function(){
    const schoolPickedByUser = this.get('schoolPickedByUser');
    return new Promise(resolve => {
      if (schoolPickedByUser)  {
        resolve(schoolPickedByUser);
      } else {
        this.get('currentUser').get('model').then(user => {
          user.get('school').then(school => {
            resolve(school);
          });
        });
      }
    });
  }),
  hasMoreThanOneSchool: computed.gt('schools.length', 1),
  allSchools: computed(function(){
    return PromiseArray.create({
      promise: this.get('currentUser.model').then(user => {
        return user.get('schools').then(schools => {
          return schools;
        });
      })
    });
  }),
  schools: computed('allSchools.[]', 'selectedSchool', function(){
    return this.get('allSchools').sortBy('title');
  }),
  selectedAcademicYear: computed('academicYearSelectedByUser', 'allAcademicYears.[]', function(){
    const academicYearSelectedByUser = this.get('academicYearSelectedByUser');
    return new Promise(resolve => {
      if (academicYearSelectedByUser)  {
        resolve(academicYearSelectedByUser);
      } else {
        this.get('allAcademicYears').then(years => {
          const year = years.sortBy('title').get('lastObject');
          resolve(year);
        });
      }
    });
  }),
  allAcademicYears: computed(function(){
    return this.get('store').findAll('academic-year');
  }),
  academicYears: computed('allAcademicYears.[]', 'academicYearSelectedByUser', function(){
    return PromiseArray.create({
      promise: this.get('allAcademicYears').then(years => {
        return years.sortBy('title');
      })
    });
  }),

  showClearFilters: computed('selectedCourses.[]', 'selectedSessionTypes.[]', 'selectedCourseLevels.[]', 'selectedCohorts.[]', {
    get() {
      const a = this.get('selectedSessionTypes');
      const b = this.get('selectedCourseLevels');
      const c = this.get('selectedCohorts');
      const d = this.get('selectedCourses');

      let results = a.concat(b, c, d);
      this.set('activeFilters', results);

      return isPresent(results) ? true : false;
    }
  }).readOnly(),

  filterTags: computed('activeFilters', {
    get() {
      const activeFilters = this.get('activeFilters');

      return activeFilters.map((filter) => {
        let hash = {};
        hash.filter = filter;

        if (typeof filter === 'number') {
          hash.class = 'tag-course-level';
          hash.name = `Course Level ${filter}`;
        } else {
          let model = filter.get('constructor.modelName');

          switch (model) {
          case 'session-type':
            hash.class = 'tag-session-type';
            hash.name = filter.get('title');
            break;
          case 'cohort':
            hash.class = 'tag-cohort';
            hash.name = `${filter.get('displayTitle')} ${filter.get('programYear.program.title')}`;
            break;
          case 'course':
            hash.class = 'tag-course';
            hash.name = filter.get('title');
            break;
          }
        }

        return hash;
      });
    }
  }),

  actions: {
    toggleSessionType(sessionType){
      if(this.get('selectedSessionTypes').includes(sessionType)){
        this.get('selectedSessionTypes').removeObject(sessionType);
      } else {
        this.get('selectedSessionTypes').pushObject(sessionType);
      }
    },
    toggleCourseLevel(level){
      if(this.get('selectedCourseLevels').includes(level)){
        this.get('selectedCourseLevels').removeObject(level);
      } else {
        this.get('selectedCourseLevels').pushObject(level);
      }
    },
    toggleCohort(cohort){
      if(this.get('selectedCohorts').includes(cohort)){
        this.get('selectedCohorts').removeObject(cohort);
      } else {
        this.get('selectedCohorts').pushObject(cohort);
      }
    },
    toggleCourse(course){
      if(this.get('selectedCourses').includes(course)){
        this.get('selectedCourses').removeObject(course);
      } else {
        this.get('selectedCourses').pushObject(course);
      }
    },

    clearFilters() {
      const selectedCourses = [];
      const selectedSessionTypes = [];
      const selectedCourseLevels = [];
      const selectedCohorts = [];

      this.setProperties({ selectedCourses, selectedSessionTypes, selectedCourseLevels, selectedCohorts });
    },

    removeFilter(filter) {
      if (typeof filter === 'number') {
        this.send('toggleCourseLevel', filter);
      } else {
        let model = filter.get('constructor.modelName');

        switch (model) {
        case 'session-type':
          this.send('toggleSessionType', filter);
          break;
        case 'cohort':
          this.send('toggleCohort', filter);
          break;
        case 'course':
          this.send('toggleCourse', filter);
          break;
        }
      }
    }
  }
});
<commit_msg>re-implemented and annotated allCohorts().<commit_after>import moment from 'moment';
import Ember from 'ember';
import DS from 'ember-data';
import momentFormat from 'ember-moment/computeds/format';

const { Component, computed, isPresent, RSVP, inject } = Ember;
const { service } = inject;
const { all, Promise } = RSVP;
const { PromiseArray } = DS;

export default Component.extend({
  init() {
    this._super(...arguments);
    //do these on setup otherwise tests were failing because
    //the old filter value hung around
    this.set('selectedSessionTypes', []);
    this.set('selectedCourseLevels', []);
    this.set('selectedCohorts', []);
    this.set('selectedCourses', []);
  },
  userEvents: service(),
  schoolEvents: service(),
  currentUser: service(),
  store: service(),
  i18n: service(),
  activeFilters: null,
  selectedDate: null,
  selectedView: null,
  selectedCohorts: [],
  selectedCourseLevels: [],
  selectedCourses: [],
  selectedSessionTypes: [],

  dueTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.dueThisDay');
  }),
  dayTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.day');
  }),
  weekTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.week');
  }),
  monthTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.month');
  }),
  loadingEventsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.loadingEvents');
  }),
  icsInstructionsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.icsInstructions');
  }),
  fromTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).startOf(this.get('selectedView')).subtract(this.get('skew'), 'days').unix();
  }),
  toTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).endOf(this.get('selectedView')).add(this.get('skew'), 'days').unix();
  }),
  clockSkew: computed('selectedView', function(){
    if(this.set('selectedView') === 'month'){
      return 6;
    }

    return 1;
  }),
  calendarDate: momentFormat('selectedDate', 'YYYY-MM-DD'),

  /**
   * @property courseLevels
   * @type {Ember.computed}
   * @public
   * @todo Review whether this needs to be a computed property, get rid of it if applicable. [ST 2017/02/07]
   */
  courseLevels: computed('selectedCourseLevels.[]', function(){
    let levels = [];
    for(let i =1; i <=5; i++){
      levels.pushObject(i);
    }

    return levels;
  }),

  /**
   * @property courses
   * @type {Ember.computed}
   * @public
   */
  courses: computed('allCourses.[]', 'selectedCourses.[]', function(){
    return this.get('allCourses');
  }),

  /**
   * @property filteredEvents
   * @type {Ember.computed}
   * @public
   */
  filteredEvents: computed(
    'ourEvents.[]',
    'eventsWithSelectedSessionTypes.[]',
    'eventsWithSelectedCourseLevels.[]',
    'eventsWithSelectedCohorts.[]',
    'eventsWithSelectedCourses.[]',
    function() {
      return new Promise(resolve => {
        let promises = [];
        let eventTypes = [
          'eventsWithSelectedSessionTypes',
          'eventsWithSelectedCourseLevels',
          'eventsWithSelectedCohorts',
          'eventsWithSelectedCourses',
        ];
        let allFilteredEvents = [];
        eventTypes.forEach(name => {
          promises.pushObject(this.get(name).then(events => {
            allFilteredEvents.pushObject(events);
          }));
        });

        all(promises).then(()=> {
          this.get('ourEvents').then(events => {
            let filteredEvents = events.filter(event => {
              return allFilteredEvents.every(arr => {
                return arr.includes(event);
              });
            });
            resolve(filteredEvents);
          });
        });
      });
    }
  ),

  /**
   * @property sessionTypes
   * @type {Ember.computed}
   * @public
   */
  sessionTypes: computed('selectedSchool.sessionTypes.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then(school => {
        school.get('sessionTypes').then(types => {
          resolve(types.sortBy('title'));
        });
      });
    });
  }),

  /**
   * @property ourEvents
   * @type {Ember.computed}
   * @protected
   */
  ourEvents: computed('mySchedule', 'fromTimeStamp', 'toTimeStamp', 'selectedSchool', 'selectedView', function(){
    return new Promise(resolve => {
      if(this.get('mySchedule')) {
        this.get('userEvents').getEvents(this.get('fromTimeStamp'), this.get('toTimeStamp')).then(userEvents => {
          resolve(userEvents);
        });
      } else {
        this.get('selectedSchool').then(school => {
          this.get('schoolEvents').getEvents(
            school.get('id'),
            this.get('fromTimeStamp'),
            this.get('toTimeStamp')
          ).then(schoolEvents => {
            resolve(schoolEvents);
          });
        });
      }
    });
  }),

  /**
   * @property eventsWithSelectedSessionTypes
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedSessionTypes: computed('ourEvents.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedSessionTypes = this.get('selectedSessionTypes').mapBy('id');
        if(selectedSessionTypes.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getSessionTypeIdForEvent(event).then(id => {
              if (selectedSessionTypes.includes(id)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourseLevels
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourseLevels: computed('ourEvents.[]', 'selectedCourseLevels.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCourseLevels = this.get('selectedCourseLevels');
        if(selectedCourseLevels.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseLevelForEvent(event).then(level => {
              if (selectedCourseLevels.includes(level)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCohorts
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCohorts: computed('ourEvents.[]', 'selectedCohorts.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCohorts = this.get('selectedCohorts').mapBy('id');
        if(selectedCohorts.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCohortIdsForEvent(event).then(cohorts => {
              if (cohorts.any(cohortId => {
                return selectedCohorts.includes(cohortId);
              })) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourses
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourses: computed('ourEvents.[]', 'selectedCourses.[]', function(){
    return new Promise(resolve => {
      let selectedCourses = this.get('selectedCourses').mapBy('id');
      this.get('ourEvents').then(events => {
        if(selectedCourses.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseIdForEvent(event).then(courseId => {
              if (selectedCourses.includes(courseId)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property allCohorts
   * @type {Ember.computed}
   * @protected
   */
  allCohorts: computed('selectedSchool', 'selectedAcademicYear', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then(school => {
        this.get('selectedAcademicYear').then(year => {
          school.getCohortsForYear(year.get('title')).then(cohorts => {
            resolve(cohorts.sortBy('displayTitle'));
          });
        });
      });
    });
  }),

  cohorts: computed('allCohorts.[].displayTitle', 'selectedCohorts.[]', function(){
    return this.get('allCohorts');
  }),

  /**
   * @property allCourses
   * @type {Ember.computed}
   * @protected
   */
  allCourses: computed('selectedSchool', 'selectedAcademicYear', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then((school) => {
        this.get('selectedAcademicYear').then((year) => {
          this.get('store').query('course', {
            filters: {
              school: school.get('id'),
              year: year.get('title')
            },
            limit: 1000
          }).then((courses) => {
            resolve(courses.sortBy('title'));
          });
        });
      });
    });
  }),

  selectedSchool: computed('schoolPickedByUser', 'currentUser.model.school', function(){
    const schoolPickedByUser = this.get('schoolPickedByUser');
    return new Promise(resolve => {
      if (schoolPickedByUser)  {
        resolve(schoolPickedByUser);
      } else {
        this.get('currentUser').get('model').then(user => {
          user.get('school').then(school => {
            resolve(school);
          });
        });
      }
    });
  }),
  hasMoreThanOneSchool: computed.gt('schools.length', 1),
  allSchools: computed(function(){
    return PromiseArray.create({
      promise: this.get('currentUser.model').then(user => {
        return user.get('schools').then(schools => {
          return schools;
        });
      })
    });
  }),
  schools: computed('allSchools.[]', 'selectedSchool', function(){
    return this.get('allSchools').sortBy('title');
  }),
  selectedAcademicYear: computed('academicYearSelectedByUser', 'allAcademicYears.[]', function(){
    const academicYearSelectedByUser = this.get('academicYearSelectedByUser');
    return new Promise(resolve => {
      if (academicYearSelectedByUser)  {
        resolve(academicYearSelectedByUser);
      } else {
        this.get('allAcademicYears').then(years => {
          const year = years.sortBy('title').get('lastObject');
          resolve(year);
        });
      }
    });
  }),
  allAcademicYears: computed(function(){
    return this.get('store').findAll('academic-year');
  }),
  academicYears: computed('allAcademicYears.[]', 'academicYearSelectedByUser', function(){
    return PromiseArray.create({
      promise: this.get('allAcademicYears').then(years => {
        return years.sortBy('title');
      })
    });
  }),

  showClearFilters: computed('selectedCourses.[]', 'selectedSessionTypes.[]', 'selectedCourseLevels.[]', 'selectedCohorts.[]', {
    get() {
      const a = this.get('selectedSessionTypes');
      const b = this.get('selectedCourseLevels');
      const c = this.get('selectedCohorts');
      const d = this.get('selectedCourses');

      let results = a.concat(b, c, d);
      this.set('activeFilters', results);

      return isPresent(results) ? true : false;
    }
  }).readOnly(),

  filterTags: computed('activeFilters', {
    get() {
      const activeFilters = this.get('activeFilters');

      return activeFilters.map((filter) => {
        let hash = {};
        hash.filter = filter;

        if (typeof filter === 'number') {
          hash.class = 'tag-course-level';
          hash.name = `Course Level ${filter}`;
        } else {
          let model = filter.get('constructor.modelName');

          switch (model) {
          case 'session-type':
            hash.class = 'tag-session-type';
            hash.name = filter.get('title');
            break;
          case 'cohort':
            hash.class = 'tag-cohort';
            hash.name = `${filter.get('displayTitle')} ${filter.get('programYear.program.title')}`;
            break;
          case 'course':
            hash.class = 'tag-course';
            hash.name = filter.get('title');
            break;
          }
        }

        return hash;
      });
    }
  }),

  actions: {
    toggleSessionType(sessionType){
      if(this.get('selectedSessionTypes').includes(sessionType)){
        this.get('selectedSessionTypes').removeObject(sessionType);
      } else {
        this.get('selectedSessionTypes').pushObject(sessionType);
      }
    },
    toggleCourseLevel(level){
      if(this.get('selectedCourseLevels').includes(level)){
        this.get('selectedCourseLevels').removeObject(level);
      } else {
        this.get('selectedCourseLevels').pushObject(level);
      }
    },
    toggleCohort(cohort){
      if(this.get('selectedCohorts').includes(cohort)){
        this.get('selectedCohorts').removeObject(cohort);
      } else {
        this.get('selectedCohorts').pushObject(cohort);
      }
    },
    toggleCourse(course){
      if(this.get('selectedCourses').includes(course)){
        this.get('selectedCourses').removeObject(course);
      } else {
        this.get('selectedCourses').pushObject(course);
      }
    },

    clearFilters() {
      const selectedCourses = [];
      const selectedSessionTypes = [];
      const selectedCourseLevels = [];
      const selectedCohorts = [];

      this.setProperties({ selectedCourses, selectedSessionTypes, selectedCourseLevels, selectedCohorts });
    },

    removeFilter(filter) {
      if (typeof filter === 'number') {
        this.send('toggleCourseLevel', filter);
      } else {
        let model = filter.get('constructor.modelName');

        switch (model) {
        case 'session-type':
          this.send('toggleSessionType', filter);
          break;
        case 'cohort':
          this.send('toggleCohort', filter);
          break;
        case 'course':
          this.send('toggleCourse', filter);
          break;
        }
      }
    }
  }
});
<|endoftext|>


<commit_before>import chalk from 'chalk'
import dns from 'native-node-dns'
import NodeCache from 'node-cache'
import EventEmitter from 'events'

// Configurations
const port = process.env.DYNSD_DNSPORT || 53,
      resolvers = {
        ipv4: [
          process.env.DYNSD_DNSALT1 || '',
          process.env.DYNSD_DNSALT2 || ''
        ],
        ipv6: [
          process.env.DYNSD_DNS6ALT1 || '',
          process.env.DYNSD_DNS6ALT2 || ''
        ]
      },
      verboseLog = ( process.env.DYNSD_VERBOSE == 'true' ),
      entries = new NodeCache({
        // From <https://www.npmjs.com/package/node-cache>
        // "You should set false if you want to save mutable objects or other complex types with mutability involved and wanted."
        useClones: false
      }),
      // Supported resources
      dnsResources = process.env.DYNSD_DNSRESOURCES ? process.env.DYNSD_DNSRESOURCES.split( ',' ) : 'A,AAAA,NS,CNAME,PTR,NAPTR,TXT,MX,SRV,SOA,TLSA'.split( ',' ),
      // Server Handlers
      tcpServer  = dns.createTCPServer(),
      udp4Server = dns.createServer( { dgram_type: { type: 'udp4', reuseAddr: true } } ),
      udp6Server = dns.createServer( { dgram_type: { type: 'udp6', reuseAddr: true } } )

function emitAsPromise( resolve, reject, me, eventName, data ) {
  const listeners = me.listeners( eventName ),
        promises = []

  if ( verboseLog )
    console.log( `[${chalk.blue('CORE')}] Dispatching '${chalk.green(eventName)}' event. Waiting for plugins to complete...` )

  for ( const listener of listeners ) {
    promises.push(
      new Promise (
        ( resolve, reject ) => {
          return listener( resolve, reject, data )
        }
      )
    )
  }

  Promise
    .all( promises )
    .catch( err => reject( err ) )
    .then( () => resolve() )
}

function request( me, req, res ) {
  const promises = []

  for (const question of req.question) {
    const entry = entries.get( question.name )

    if ( entry ) {
      for ( const resource of dnsResources ) {
        if ( resource in entry )
          res.answer
            .push( dns[ resource ]( entry[ resource ] ) )
      }
    } else {
      promises.push(
        recurse( me, question, req, res )
      )
    }
  }

  Promise
    .all( promises )
    .then(
      () => {
        return new Promise(
          ( resolve, reject ) => {
            if ( promises.length )
              emitAsPromise(
                resolve,
                reject,
                me,
                'resolve.external',
                {
                  req,
                  res
                }
              )
            else
              emitAsPromise(
                resolve,
                reject,
                me,
                'resolve.internal',
                {
                  req,
                  res
                }
              )
          }
        )
      }
    )
    .then( () => res.send() )
    .catch( err => console.error( err ) )
}

function recurse( me, question, req, res ) {
  const ipFamily = req.address.family.toLowerCase(),
        resolver = resolvers[ ipFamily ][ Math.round( Math.random() ) ]

  return new Promise(
    ( resolve, reject ) => {
      if (resolver)
      {
        dns
          .Request({
            question: question,
            server: {
              address: resolver,
              'port': 53,
              'type': 'udp'
            },
            timeout: 1000
          })
          .on( 'timeout', () => reject( `[${chalk.blue('DNS')}] Recursive question ${chalk.green(question.name)} went in timeout with resolver ${chalk.gray(`${resolver}:53`)}` ) )
          .on( 'message',
            ( err, msg ) => {
              for (const answer of msg.answer) {
                res.answer.push( answer )
              }
            }
          )
          .on( 'end', resolve )
          .send()
      }
      else
      {
        if ( verboseLog )
          console.error(`[${chalk.blue('CORE')}] No recursive DNS provided. Will not resolve the requested DNS name ${chalk.green(question.name)}.`)
        resolve()
      }
    }
  )
}

export default class extends EventEmitter {
  constructor() {
    // Inherit EventEmitter methods
    super()
    // Include the chalk reference
    this.chalk = chalk
  }

  start() {
    const me = this

    Promise
      .resolve()
      .then(
        () => {
          return new Promise(
            ( resolve, reject ) => {
              emitAsPromise(
                resolve,
                reject,
                me,
                'init',
                {
                  entries
                }
              )
            }
          )
        }
      )
      .then(
        () => {
          return new Promise (
            ( resolve, reject ) => {
              tcpServer
                .on( 'socketError', ( e ) => reject( `[${chalk.blue('DNS')}] ${chalk.green('TCP')}: ${e.message}` ) )
                .on( 'request', ( req, res ) => request( me, req, res ) )
                .on( 'listening', () => {
                  const uri = `[::]:${port}/tcp`
                  console.log( `[${chalk.blue('DNS')}] Listening on ${chalk.blue(uri)}` )
                  resolve()
                })
                .serve( port )
            }
          )
        }
      )
      .then(
        () => {
          return new Promise (
            ( resolve, reject ) => {
              udp4Server
                .on( 'socketError', ( e ) => reject( `[${chalk.blue('DNS')}] ${chalk.green('UDP4')}: ${e.message}` ) )
                .on( 'request', ( req, res ) => request( me, req, res ) )
                .on( 'listening', () => {
                  const uri = `0.0.0.0:${port}/udp`
                  console.log( `[${chalk.blue('DNS')}] Listening on ${chalk.blue(uri)}` )
                  resolve()
                })
                .serve( port )
            }
          )
        }
      )
      .then(
        () => {
          return new Promise (
            ( resolve, reject ) => {
              udp6Server
                .on( 'socketError', ( e ) => reject( `[${chalk.blue('DNS')}] ${chalk.green('UDP6')}: ${e.message}` ) )
                .on( 'request', ( req, res ) => request( me, req, res ) )
                .on( 'listening', () => {
                  const uri = `[::]:${port}/udp`
                  console.log( `[${chalk.blue('DNS')}] Listening on ${chalk.blue(uri)}` )
                  resolve()
                })
                .serve( port )
            }
          )
        }
      )
      .catch( err => console.error( err ) )
  }
}<commit_msg>Improve error message<commit_after>import chalk from 'chalk'
import dns from 'native-node-dns'
import NodeCache from 'node-cache'
import EventEmitter from 'events'

// Configurations
const port = process.env.DYNSD_DNSPORT || 53,
      resolvers = {
        ipv4: [
          process.env.DYNSD_DNSALT1 || '',
          process.env.DYNSD_DNSALT2 || ''
        ],
        ipv6: [
          process.env.DYNSD_DNS6ALT1 || '',
          process.env.DYNSD_DNS6ALT2 || ''
        ]
      },
      verboseLog = ( process.env.DYNSD_VERBOSE == 'true' ),
      entries = new NodeCache({
        // From <https://www.npmjs.com/package/node-cache>
        // "You should set false if you want to save mutable objects or other complex types with mutability involved and wanted."
        useClones: false
      }),
      // Supported resources
      dnsResources = process.env.DYNSD_DNSRESOURCES ? process.env.DYNSD_DNSRESOURCES.split( ',' ) : 'A,AAAA,NS,CNAME,PTR,NAPTR,TXT,MX,SRV,SOA,TLSA'.split( ',' ),
      // Server Handlers
      tcpServer  = dns.createTCPServer(),
      udp4Server = dns.createServer( { dgram_type: { type: 'udp4', reuseAddr: true } } ),
      udp6Server = dns.createServer( { dgram_type: { type: 'udp6', reuseAddr: true } } )

function emitAsPromise( resolve, reject, me, eventName, data ) {
  const listeners = me.listeners( eventName ),
        promises = []

  if ( verboseLog )
    console.log( `[${chalk.blue('CORE')}] Dispatching '${chalk.green(eventName)}' event. Waiting for plugins to complete...` )

  for ( const listener of listeners ) {
    promises.push(
      new Promise (
        ( resolve, reject ) => {
          return listener( resolve, reject, data )
        }
      )
    )
  }

  Promise
    .all( promises )
    .catch( err => reject( err ) )
    .then( () => resolve() )
}

function request( me, req, res ) {
  const promises = []

  for (const question of req.question) {
    const entry = entries.get( question.name )

    if ( entry ) {
      for ( const resource of dnsResources ) {
        if ( resource in entry )
          res.answer
            .push( dns[ resource ]( entry[ resource ] ) )
      }
    } else {
      promises.push(
        recurse( me, question, req, res )
      )
    }
  }

  Promise
    .all( promises )
    .then(
      () => {
        return new Promise(
          ( resolve, reject ) => {
            if ( promises.length )
              emitAsPromise(
                resolve,
                reject,
                me,
                'resolve.external',
                {
                  req,
                  res
                }
              )
            else
              emitAsPromise(
                resolve,
                reject,
                me,
                'resolve.internal',
                {
                  req,
                  res
                }
              )
          }
        )
      }
    )
    .then( () => res.send() )
    .catch( err => console.error( err ) )
}

function recurse( me, question, req, res ) {
  const ipFamily = req.address.family.toLowerCase(),
        resolver = resolvers[ ipFamily ][ Math.round( Math.random() ) ]

  return new Promise(
    ( resolve, reject ) => {
      if (resolver)
      {
        dns
          .Request({
            question: question,
            server: {
              address: resolver,
              'port': 53,
              'type': 'udp'
            },
            timeout: 1000
          })
          .on( 'timeout', () => reject( `[${chalk.blue('DNS')}] Recursive question ${chalk.green(question.name)} went in timeout with resolver ${chalk.gray(`${resolver}:53`)}` ) )
          .on( 'message',
            ( err, msg ) => {
              for (const answer of msg.answer) {
                res.answer.push( answer )
              }
            }
          )
          .on( 'end', resolve )
          .send()
      }
      else
      {
        if ( verboseLog )
          console.error(`[${chalk.blue('CORE')}] No recursive DNS provided. Will not resolve the requested DNS name ${chalk.green(question.name)}. Configured resolvers: `, resolvers)
        resolve()
      }
    }
  )
}

export default class extends EventEmitter {
  constructor() {
    // Inherit EventEmitter methods
    super()
    // Include the chalk reference
    this.chalk = chalk
  }

  start() {
    const me = this

    Promise
      .resolve()
      .then(
        () => {
          return new Promise(
            ( resolve, reject ) => {
              emitAsPromise(
                resolve,
                reject,
                me,
                'init',
                {
                  entries
                }
              )
            }
          )
        }
      )
      .then(
        () => {
          return new Promise (
            ( resolve, reject ) => {
              tcpServer
                .on( 'socketError', ( e ) => reject( `[${chalk.blue('DNS')}] ${chalk.green('TCP')}: ${e.message}` ) )
                .on( 'request', ( req, res ) => request( me, req, res ) )
                .on( 'listening', () => {
                  const uri = `[::]:${port}/tcp`
                  console.log( `[${chalk.blue('DNS')}] Listening on ${chalk.blue(uri)}` )
                  resolve()
                })
                .serve( port )
            }
          )
        }
      )
      .then(
        () => {
          return new Promise (
            ( resolve, reject ) => {
              udp4Server
                .on( 'socketError', ( e ) => reject( `[${chalk.blue('DNS')}] ${chalk.green('UDP4')}: ${e.message}` ) )
                .on( 'request', ( req, res ) => request( me, req, res ) )
                .on( 'listening', () => {
                  const uri = `0.0.0.0:${port}/udp`
                  console.log( `[${chalk.blue('DNS')}] Listening on ${chalk.blue(uri)}` )
                  resolve()
                })
                .serve( port )
            }
          )
        }
      )
      .then(
        () => {
          return new Promise (
            ( resolve, reject ) => {
              udp6Server
                .on( 'socketError', ( e ) => reject( `[${chalk.blue('DNS')}] ${chalk.green('UDP6')}: ${e.message}` ) )
                .on( 'request', ( req, res ) => request( me, req, res ) )
                .on( 'listening', () => {
                  const uri = `[::]:${port}/udp`
                  console.log( `[${chalk.blue('DNS')}] Listening on ${chalk.blue(uri)}` )
                  resolve()
                })
                .serve( port )
            }
          )
        }
      )
      .catch( err => console.error( err ) )
  }
}<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import Icon from './Icon';
import Spinner from './Spinner';

export default class SearchField extends React.PureComponent {
  static propTypes = {
    className: PropTypes.string,
    style: PropTypes.object,
    onChange: PropTypes.func.isRequired,
    onKeyDown: PropTypes.func,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    defaultValue: PropTypes.string,
    placeholder: PropTypes.string.isRequired,
    borderless: PropTypes.bool.isRequired,
    searching: PropTypes.bool.isRequired,
    autofocus: PropTypes.bool.isRequired
  };

  static defaultProps = {
    placeholder: 'Search…',
    borderless: false,
    searching: false,
    autofocus: false
  };

  componentDidMount() {
    if (this.props.autofocus) {
      this.focus();
    }
  }

  clear() {
    this._inputNode.value = '';
  }

  focus() {
    this._inputNode.focus();
  }

  blur() {
    this._inputNode.blur();
  }

  render() {
    return (
      <div
        className={classNames('dark-gray relative', this.props.className)}
        style={this.props.style}
      >
        {this.renderIcon()}
        <input
          type="text"
          className={classNames('input', { borderless: this.props.borderless })}
          style={{
            margin: 0,
            color: 'inherit',
            fontSize: 'inherit',
            fontWeight: 'inherit',
            paddingLeft: '2em'
          }}
          ref={(_inputNode) => this._inputNode = _inputNode}
          defaultValue={this.props.defaultValue}
          onChange={this.handleInputChange}
          onKeyDown={this.props.onKeyDown}
          onFocus={this.props.onFocus}
          onBlur={this.props.onBlur}
          placeholder={this.props.placeholder}
        />
      </div>
    );
  }

  renderIcon() {
    const iconSize = '1.1em';
    const className = 'absolute pointer-events-none';
    const style = { left: '.6em', top: '.64em' };

    if (this.props.searching) {
      return (
        <Spinner
          size={iconSize}
          color={false}
          className={className}
          style={style}
        />
      );
    } else {
      return (
        <Icon
          icon="search"
          className={className}
          style={{ color: 'currentColor', width: iconSize, height: iconSize, ...style }}
        />
      );
    }
  }

  handleInputChange = (evt) => {
    // Get a copy of the target otherwise the event will be cleared between now
    // and when the timeout happens
    const { value } = evt.target;

    // If a timeout is already present, clear it since the user is still typing
    if (this._timeout) {
      clearTimeout(this._timeout);
    }

    // Instead of doing a search on each keypress, do it a few ms after they've
    // stopped typing
    this._timeout = setTimeout(() => {
      this.props.onChange(value);
      delete this._timeout;
    }, 100);
  };
}
<commit_msg>Make the search icon slightly smaller<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import Icon from './Icon';
import Spinner from './Spinner';

export default class SearchField extends React.PureComponent {
  static propTypes = {
    className: PropTypes.string,
    style: PropTypes.object,
    onChange: PropTypes.func.isRequired,
    onKeyDown: PropTypes.func,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    defaultValue: PropTypes.string,
    placeholder: PropTypes.string.isRequired,
    borderless: PropTypes.bool.isRequired,
    searching: PropTypes.bool.isRequired,
    autofocus: PropTypes.bool.isRequired
  };

  static defaultProps = {
    placeholder: 'Search…',
    borderless: false,
    searching: false,
    autofocus: false
  };

  componentDidMount() {
    if (this.props.autofocus) {
      this.focus();
    }
  }

  clear() {
    this._inputNode.value = '';
  }

  focus() {
    this._inputNode.focus();
  }

  blur() {
    this._inputNode.blur();
  }

  render() {
    return (
      <div
        className={classNames('dark-gray relative', this.props.className)}
        style={this.props.style}
      >
        {this.renderIcon()}
        <input
          type="text"
          className={classNames('input', { borderless: this.props.borderless })}
          style={{
            margin: 0,
            color: 'inherit',
            fontSize: 'inherit',
            fontWeight: 'inherit',
            paddingLeft: '2em'
          }}
          ref={(_inputNode) => this._inputNode = _inputNode}
          defaultValue={this.props.defaultValue}
          onChange={this.handleInputChange}
          onKeyDown={this.props.onKeyDown}
          onFocus={this.props.onFocus}
          onBlur={this.props.onBlur}
          placeholder={this.props.placeholder}
        />
      </div>
    );
  }

  renderIcon() {
    const iconSize = '.95em';
    const className = 'absolute pointer-events-none';
    const style = { left: '.7em', top: '.7em' };

    if (this.props.searching) {
      return (
        <Spinner
          size={iconSize}
          color={false}
          className={className}
          style={style}
        />
      );
    } else {
      return (
        <Icon
          icon="search"
          className={className}
          style={{ color: 'currentColor', width: iconSize, height: iconSize, ...style }}
        />
      );
    }
  }

  handleInputChange = (evt) => {
    // Get a copy of the target otherwise the event will be cleared between now
    // and when the timeout happens
    const { value } = evt.target;

    // If a timeout is already present, clear it since the user is still typing
    if (this._timeout) {
      clearTimeout(this._timeout);
    }

    // Instead of doing a search on each keypress, do it a few ms after they've
    // stopped typing
    this._timeout = setTimeout(() => {
      this.props.onChange(value);
      delete this._timeout;
    }, 100);
  };
}
<|endoftext|>


<commit_before>'use strict';

var blocks = require('block-elements'),
    dom = exports

blocks = blocks.map(function (name) {
  return name.toUpperCase()
})

// For our purposes, LIs should be considered block elements;
// otherwise, the sanitizer will wrap them in a P.
if (blocks.indexOf('LI') < 0)
  blocks.push('LI')

/**
 * isElement(node) determines if a object is an element.
 *
 * @param {Node} node
 * @return {Boolean}
 */
exports.isElement = function (node) {
  return node && node.nodeType === Node.ELEMENT_NODE
}

/**
 * isText(node) determines if an object is a text node.
 *
 * @param {Node} node
 * @return {Boolean}
 */
exports.isText = function (node) {
  return node && node.nodeType === Node.TEXT_NODE
}

/**
 * isBlock(elem) determines if an element is a block element
 * according to the above RegExp. Hardly comprehensive.
 *
 * @param {Node} elem
 * @return {Boolean}
 */
exports.isBlock = function (elem) {
  return !!(exports.isElement(elem) && blocks.indexOf(elem.nodeName) >= 0)
}

/**
 * remove(node) removes an element from its parent. Returns the same
 * node.
 *
 * @param {Node} node
 * @return {Node}
 */
exports.remove = function (node) {
  if (node.parentNode)
    node.parentNode.removeChild(node)

  return node
}

/**
 * after(node, after) inserts the node 'after' after the node 'node'.
 * Returns the inserted node.
 *
 * @param {Node} node
 * @param {Node} after
 * @return {Node}
 */
exports.after = function (node, after) {
  return node.parentNode.insertBefore(after, node.nextSibling)
}

/**
 * before(node, before) inserts the node 'before' before the node 'node'.
 * Returns the inserted node.
 *
 * @param {Node} node
 * @param {Node} before
 * @return {Node}
 */
exports.before = function (node, before) {
  return node.parentNode.insertBefore(before, node)
}

/**
 * replace(node, other) replaces the node 'node' with the node 'other'.
 * Returns the replaced node ('node', in this case).
 *
 * @param {Node} node
 * @param {Node} other
 * @return {Node}
 */
exports.replace = function (node, other) {
  return node.parentNode.replaceChild(other, node)
}

/**
 * prepend(child, parent) is like parent.appendChild(child), but the
 * child will become the parent’s firstChild. Returns the appended
 * child.
 *
 * @param {Node} child
 * @param {Node} parent
 * @return {Node}
 */
exports.prepend = function (child, parent) {
  return parent.insertBefore(child, parent.firstChild)
}

/**
 * create(tag) is a thin wrapper around document.createElement().
 *
 * @param {String} tag
 * @return {Element}
 */
exports.create = function (tag) {
  return document.createElement(tag)
}

/**
 * ancestor(child [, name]) returns the ancestor of the given child
 * whose nodeName matches ‘name.’ If no name is given, returns the
 * immediate ancestor of the child. In either case, returns null if
 * an appropriate ancestor cannot be found.
 *
 * @param {Node} child
 * @param {String} name
 * @return {Element}
 */
exports.ancestor = function (child, name) {
  var parent = child.parentNode

  if (!name)
    return parent

  while (parent && parent.nodeName !== name)
    parent = parent.parentNode

  return parent
}

/**
 * splitAt(node, until) splits a DOM tree at the given node up until
 * the node matching ‘until.’ ‘until’ can be either a string (nodeName)
 * or an actual element.
 *
 * @param {Node} node
 * @param {String || Element} until
 * @return {Element}
 */
exports.splitAt = function (node, until) {
  var parent = node.parentNode,
      nextParent

  until = until || 'SECTION'
  if (typeof until === 'string')
    until = until.toUpperCase()

  while (parent && parent !== until && parent.nodeName !== until) {
    if (node === parent.lastChild) {
      node = parent
      parent = node.parentNode
      continue
    }

    nextParent = parent.cloneNode(false)
    while (node.nextSibling)
      nextParent.appendChild(dom.remove(node.nextSibling))

    dom.after(parent, nextParent)

    node = parent
    parent = node.parentNode
  }

  return node
}

/**
 * ancestorsAsArray(element) gets the ancestors of the given array,
 * up to the first SECTION, and returns them as an array in descending
 * (“highest” ancestor first) order.
 *
 * @param {Element} element
 * @return {Array}
 */
var ancestorsAsArray =
exports._ancestorsAsArray = function (element) {
  var ancestors = [element]

  while (element.parentNode && element.parentNode.nodeName !== 'SECTION') {
    element = element.parentNode
    ancestors.unshift(element)
  }

  return ancestors
}

/**
 * _joinElements(elements) takes an array of DOM elements and returns
 * those elements appended to each other, with the first element as
 * topmost parent and last element as “lowest” child.
 *
 * @param {Array} elements
 * @return {Element}
 */
exports._joinElements = function (elements) {
  var root = elements[0],
      i = 1

  while (elements[i]) {
    elements[i - 1].appendChild(elements[i])
    i += 1
  }

  return root
}

/**
 * _merge(before, after) combines the similar ancestors of two
 * adjacent paragraphs.
 *
 * @param {Element} before
 * @param {Element} after
 */
exports._merge = function (before, after) {
  var len,
      i

  before = ancestorsAsArray(before)
  after = ancestorsAsArray(after)

  if (before[0] === after[0])
    return

  len = Math.min(before.length, after.length) - 1
  for (i = 0; i < len; i += 1) {
    if (before[i].nodeName !== after[i].nodeName)
      break

    while (after[i].lastChild)
      dom.after(before[i + 1], dom.remove(after[i].lastChild))

    dom.remove(after[i])
  }
}
<commit_msg>dom: remove prepend()<commit_after>'use strict';

var blocks = require('block-elements'),
    dom = exports

blocks = blocks.map(function (name) {
  return name.toUpperCase()
})

// For our purposes, LIs should be considered block elements;
// otherwise, the sanitizer will wrap them in a P.
if (blocks.indexOf('LI') < 0)
  blocks.push('LI')

/**
 * isElement(node) determines if a object is an element.
 *
 * @param {Node} node
 * @return {Boolean}
 */
exports.isElement = function (node) {
  return node && node.nodeType === Node.ELEMENT_NODE
}

/**
 * isText(node) determines if an object is a text node.
 *
 * @param {Node} node
 * @return {Boolean}
 */
exports.isText = function (node) {
  return node && node.nodeType === Node.TEXT_NODE
}

/**
 * isBlock(elem) determines if an element is a block element
 * according to the above RegExp. Hardly comprehensive.
 *
 * @param {Node} elem
 * @return {Boolean}
 */
exports.isBlock = function (elem) {
  return !!(exports.isElement(elem) && blocks.indexOf(elem.nodeName) >= 0)
}

/**
 * remove(node) removes an element from its parent. Returns the same
 * node.
 *
 * @param {Node} node
 * @return {Node}
 */
exports.remove = function (node) {
  if (node.parentNode)
    node.parentNode.removeChild(node)

  return node
}

/**
 * after(node, after) inserts the node 'after' after the node 'node'.
 * Returns the inserted node.
 *
 * @param {Node} node
 * @param {Node} after
 * @return {Node}
 */
exports.after = function (node, after) {
  return node.parentNode.insertBefore(after, node.nextSibling)
}

/**
 * before(node, before) inserts the node 'before' before the node 'node'.
 * Returns the inserted node.
 *
 * @param {Node} node
 * @param {Node} before
 * @return {Node}
 */
exports.before = function (node, before) {
  return node.parentNode.insertBefore(before, node)
}

/**
 * replace(node, other) replaces the node 'node' with the node 'other'.
 * Returns the replaced node ('node', in this case).
 *
 * @param {Node} node
 * @param {Node} other
 * @return {Node}
 */
exports.replace = function (node, other) {
  return node.parentNode.replaceChild(other, node)
}

/**
 * create(tag) is a thin wrapper around document.createElement().
 *
 * @param {String} tag
 * @return {Element}
 */
exports.create = function (tag) {
  return document.createElement(tag)
}

/**
 * ancestor(child [, name]) returns the ancestor of the given child
 * whose nodeName matches ‘name.’ If no name is given, returns the
 * immediate ancestor of the child. In either case, returns null if
 * an appropriate ancestor cannot be found.
 *
 * @param {Node} child
 * @param {String} name
 * @return {Element}
 */
exports.ancestor = function (child, name) {
  var parent = child.parentNode

  if (!name)
    return parent

  while (parent && parent.nodeName !== name)
    parent = parent.parentNode

  return parent
}

/**
 * splitAt(node, until) splits a DOM tree at the given node up until
 * the node matching ‘until.’ ‘until’ can be either a string (nodeName)
 * or an actual element.
 *
 * @param {Node} node
 * @param {String || Element} until
 * @return {Element}
 */
exports.splitAt = function (node, until) {
  var parent = node.parentNode,
      nextParent

  until = until || 'SECTION'
  if (typeof until === 'string')
    until = until.toUpperCase()

  while (parent && parent !== until && parent.nodeName !== until) {
    if (node === parent.lastChild) {
      node = parent
      parent = node.parentNode
      continue
    }

    nextParent = parent.cloneNode(false)
    while (node.nextSibling)
      nextParent.appendChild(dom.remove(node.nextSibling))

    dom.after(parent, nextParent)

    node = parent
    parent = node.parentNode
  }

  return node
}

/**
 * ancestorsAsArray(element) gets the ancestors of the given array,
 * up to the first SECTION, and returns them as an array in descending
 * (“highest” ancestor first) order.
 *
 * @param {Element} element
 * @return {Array}
 */
var ancestorsAsArray =
exports._ancestorsAsArray = function (element) {
  var ancestors = [element]

  while (element.parentNode && element.parentNode.nodeName !== 'SECTION') {
    element = element.parentNode
    ancestors.unshift(element)
  }

  return ancestors
}

/**
 * _joinElements(elements) takes an array of DOM elements and returns
 * those elements appended to each other, with the first element as
 * topmost parent and last element as “lowest” child.
 *
 * @param {Array} elements
 * @return {Element}
 */
exports._joinElements = function (elements) {
  var root = elements[0],
      i = 1

  while (elements[i]) {
    elements[i - 1].appendChild(elements[i])
    i += 1
  }

  return root
}

/**
 * _merge(before, after) combines the similar ancestors of two
 * adjacent paragraphs.
 *
 * @param {Element} before
 * @param {Element} after
 */
exports._merge = function (before, after) {
  var len,
      i

  before = ancestorsAsArray(before)
  after = ancestorsAsArray(after)

  if (before[0] === after[0])
    return

  len = Math.min(before.length, after.length) - 1
  for (i = 0; i < len; i += 1) {
    if (before[i].nodeName !== after[i].nodeName)
      break

    while (after[i].lastChild)
      dom.after(before[i + 1], dom.remove(after[i].lastChild))

    dom.remove(after[i])
  }
}
<|endoftext|>


<commit_before>import { isFn, shieldProps } from './utils'
import { parseHref } from './utils-path'

function validateClick(e) {
  return !(e.defaultPrevented || e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
}

export function createListener(action) {
  return function clickHandler(e) {
    if (validateClick(e)) {
      e.preventDefault()
      action()
    }
  }
}

const defaultStyle = { touchAction: 'manipulation', msTouchAction: 'manipulation' }

export const Anchor = p => {
  let props = shieldProps(p, 'createElement', 'ultra', 'style', 'state', 'title')
  let { href, createElement, ultra, style, state, title } = props
  let loc = Object.assign(parseHref(href), { state, title })
  props.onClick = createListener(ultra.push.bind(null, loc))
  props.style = Object.assign({}, defaultStyle, style)
  return isFn(createElement) ? createElement('a', props) : props
}
<commit_msg>use getUltra to make ultra lazy in Anchor closes #39<commit_after>import { isFn, shieldProps } from './utils'
import { parseHref } from './utils-path'

function validateClick(e) {
  return !(e.defaultPrevented || e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
}

export function createListener(action) {
  return function clickHandler(e) {
    if (validateClick(e)) {
      e.preventDefault()
      action()
    }
  }
}

const defaultStyle = { touchAction: 'manipulation', msTouchAction: 'manipulation' }

export const Anchor = p => {
  let props = shieldProps(p, 'createElement', 'getUltra', 'style', 'state', 'title')
  let { href, createElement, getUltra, style, state, title } = props
  let loc = Object.assign(parseHref(href), { state, title })
  props.onClick = createListener(() => getUltra().push(loc))
  props.style = Object.assign({}, defaultStyle, style)
  return isFn(createElement) ? createElement('a', props) : props
}
<|endoftext|>


<commit_before>import { VNode } from "./VNode"

const stack = new Array(128)
let stackIndex = 0
let bodyNode = null

const elementOpen = function(type, props)
{
	const parent = stack[stackIndex]

	let node = parent.children[parent.index]
	if(!node)
	{
		const element = document.createElement(type)
		node = new VNode(parent.index, type, props, element, parent)

		if(props) {
			for(let key in props) {
				setProp(element, key, props[key])
			}
		}

		parent.children[parent.index] = node
	}
	else
	{
		if(node.type !== type)
		{
			if(node.component) {
				node.component.remove()
				node.component = null
			}

			const prevElement = node.element
			const element = document.createElement(type)
			while(prevElement.firstChild) {
				element.appendChild(prevElement.firstChild)
			}

			if(props)
			{
				for(let key in props) {
					setProp(element, key, props[key])
				}
				node.props = props
			}

			prevElement.parentElement.replaceChild(element, prevElement)
			node.type = type
			node.element = element
		}
		else
		{
			const element = node.element
			const prevProps = node.props

			if(props !== prevProps)
			{
				if(props)
				{
					if(prevProps)
					{
						for(let key in prevProps) {
							if(props[key] === undefined) {
								unsetProp(element, key)
							}
						}

						for(let key in props) {
							const value = props[key]
							if(value !== prevProps[key]) {
								setProp(element, key, value)
							}
						}
					}
					else
					{
						for(let key in props) {
							setProp(element, key, props[key])
						}
					}

					node.props = props
				}
				else
				{
					if(prevProps)
					{
						for(let key in prevProps) {
							unsetProp(element, key)
						}

						node.props = null
					}
				}
			}
		}
	}

	parent.index++
	stackIndex++
	stack[stackIndex] = node

	return node
}

const elementClose = function(type)
{
	const node = stack[stackIndex]
	const parent = stack[stackIndex - 1]

	if(node.type !== type) {
		console.error("type-error")
	}

	if(node.index !== node.children.length) {
		removeUnusedNodes(node)
	}

	node.index = 0
	stackIndex--

	if(!node.element.parentElement) {
		parent.element.appendChild(node.element)
	}
}

const elementVoid = function(type, props)
{
	const node = elementOpen(type, props)
	elementClose(type)

	return node
}

const componentVoid = function(componentCls, props)
{
	const parent = stack[stackIndex]

	let node = parent.children[parent.index]
	if(!node) {
		return createComponent(componentCls, node, props)
	}
	else
	{
		const component = node.component
		if(component)
		{
			if(component.constructor !== componentCls) {
				component.remove()
				createComponent(componentCls, node, props)
			}
			else
			{
				if(props) {
					for(let key in props) {
						component[key] = props[key]
					}
				}

				if(component.dirty) {
					component.render()
					component.dirty = false
				}
				else {
					parent.index++
				}
			}
		}
		else {
			createComponent(componentCls, node, props)
		}
	}

	return node.component
}

const createComponent = function(componentCls, node, props)
{
	const component = new componentCls()

	if(component.mount) {
		component.mount()
	}

	if(props) {
		for(let key in props) {
			component[key] = props[key]
		}
	}

	component.render()
	component.dirty = false
	component.depth = stackIndex + 1

	node = stack[stackIndex + 1]
	if(node) {
		node.component = component
		component.vnode = node
	}

	return component
}

const text = function(txt)
{
	const parent = stack[stackIndex]
	let node = parent.children[parent.index]
	if(!node) {
		const element = document.createTextNode(txt)
		const node = new VNode(parent.index, "#text", null, element, parent)
		parent.children[parent.index] = node
		parent.element.appendChild(element)
	}
	else
	{
		if(node.type !== "#text") {
			const element = document.createTextNode(txt)
			node.element.parentElement.replaceChild(element, node.element)
			node.element = element
		}
		else if(node.element.nodeValue !== txt) {
			node.element.nodeValue = txt
		}
	}

	parent.index++

	return node
}

const setProp = function(element, name, value)
{
	if(name === "class") {
		element.className = value
	}
	else if(name === "style")
	{
		if(typeof value === "object") {
			const elementStyle = element.style
			for(let key in value) {
				elementStyle[key] = value[key]
			}
		}
		else {
			element.style.cssText = value
		}
	}
	else if(name[0] === "o" && name[1] === "n") {
		element[name] = value
	}
	else {
		element.setAttribute(name, value)
	}
}

const unsetProp = function(element, name)
{
	if(name === "class") {
		element.className = ""
	}
	else if(name === "style") {
		element.style.cssText = ""
	}
	else if(name[0] === "o" && name[1] === "n") {
		element[name] = null
	}
	else {
		element.removeAttribute(name)
	}
}

const render = function(component, parentElement)
{
	if(!bodyNode) {
		bodyNode = new VNode(0, "body", null, parentElement, null)
	}

	stackIndex = 0
	stack[0] = bodyNode

	componentVoid(component)

	bodyNode.index = 0
}

const renderInstance = function(instance)
{
	const vnode = instance.vnode
	const parentVNode = vnode.parent
	parentVNode.index = vnode.id

	stackIndex = instance.depth
	stack[instance.depth] = parentVNode

	instance.render()
	instance.dirty = false

	parentVNode.index = 0
}

const removeUnusedNodes = function(node)
{
	const children = node.children
	for(let n = node.index; n < children.length; n++) {
		const child = children[n]
		removeNode(child)
	}

	children.length = node.index
}

const removeNode = function(node)
{
	if(node.component) {
		node.component.remove()
	}

	node.children.length = 0
	node.element.parentElement.removeChild(node.element)

	const children = node.children
	for(let n = 0; n < children.length; n++) {
		const child = children[n]
		removeNode(child)
	}
}

const removeAll = function() {
	removeUnusedNodes(bodyNode)
}

export {
	elementOpen,
	elementClose,
	elementVoid,
	componentVoid,
	text,
	render,
	renderInstance,
	removeAll
}<commit_msg>Change component mount order<commit_after>import { VNode } from "./VNode"

const stack = new Array(128)
let stackIndex = 0
let bodyNode = null

const elementOpen = function(type, props)
{
	const parent = stack[stackIndex]

	let node = parent.children[parent.index]
	if(!node)
	{
		const element = document.createElement(type)
		node = new VNode(parent.index, type, props, element, parent)

		if(props) {
			for(let key in props) {
				setProp(element, key, props[key])
			}
		}

		parent.children[parent.index] = node
	}
	else
	{
		if(node.type !== type)
		{
			if(node.component) {
				node.component.remove()
				node.component = null
			}

			const prevElement = node.element
			const element = document.createElement(type)
			while(prevElement.firstChild) {
				element.appendChild(prevElement.firstChild)
			}

			if(props)
			{
				for(let key in props) {
					setProp(element, key, props[key])
				}
				node.props = props
			}

			prevElement.parentElement.replaceChild(element, prevElement)
			node.type = type
			node.element = element
		}
		else
		{
			const element = node.element
			const prevProps = node.props

			if(props !== prevProps)
			{
				if(props)
				{
					if(prevProps)
					{
						for(let key in prevProps) {
							if(props[key] === undefined) {
								unsetProp(element, key)
							}
						}

						for(let key in props) {
							const value = props[key]
							if(value !== prevProps[key]) {
								setProp(element, key, value)
							}
						}
					}
					else
					{
						for(let key in props) {
							setProp(element, key, props[key])
						}
					}

					node.props = props
				}
				else
				{
					if(prevProps)
					{
						for(let key in prevProps) {
							unsetProp(element, key)
						}

						node.props = null
					}
				}
			}
		}
	}

	parent.index++
	stackIndex++
	stack[stackIndex] = node

	return node
}

const elementClose = function(type)
{
	const node = stack[stackIndex]
	const parent = stack[stackIndex - 1]

	if(node.type !== type) {
		console.error("type-error")
	}

	if(node.index !== node.children.length) {
		removeUnusedNodes(node)
	}

	node.index = 0
	stackIndex--

	if(!node.element.parentElement) {
		parent.element.appendChild(node.element)
	}
}

const elementVoid = function(type, props)
{
	const node = elementOpen(type, props)
	elementClose(type)

	return node
}

const componentVoid = function(componentCls, props)
{
	const parent = stack[stackIndex]

	let node = parent.children[parent.index]
	if(!node) {
		return createComponent(componentCls, node, props)
	}
	else
	{
		const component = node.component
		if(component)
		{
			if(component.constructor !== componentCls) {
				component.remove()
				createComponent(componentCls, node, props)
			}
			else
			{
				if(props) {
					for(let key in props) {
						component[key] = props[key]
					}
				}

				if(component.dirty) {
					component.render()
					component.dirty = false
				}
				else {
					parent.index++
				}
			}
		}
		else {
			createComponent(componentCls, node, props)
		}
	}

	return node.component
}

const createComponent = function(componentCls, node, props)
{
	const component = new componentCls()

	if(props) {
		for(let key in props) {
			component[key] = props[key]
		}
	}

	if(component.mount) {
		component.mount()
	}

	component.render()
	component.dirty = false
	component.depth = stackIndex + 1

	node = stack[stackIndex + 1]
	if(node) {
		node.component = component
		component.vnode = node
	}

	return component
}

const text = function(txt)
{
	const parent = stack[stackIndex]
	let node = parent.children[parent.index]
	if(!node) {
		const element = document.createTextNode(txt)
		const node = new VNode(parent.index, "#text", null, element, parent)
		parent.children[parent.index] = node
		parent.element.appendChild(element)
	}
	else
	{
		if(node.type !== "#text") {
			const element = document.createTextNode(txt)
			node.element.parentElement.replaceChild(element, node.element)
			node.element = element
		}
		else if(node.element.nodeValue !== txt) {
			node.element.nodeValue = txt
		}
	}

	parent.index++

	return node
}

const setProp = function(element, name, value)
{
	if(name === "class") {
		element.className = value
	}
	else if(name === "style")
	{
		if(typeof value === "object") {
			const elementStyle = element.style
			for(let key in value) {
				elementStyle[key] = value[key]
			}
		}
		else {
			element.style.cssText = value
		}
	}
	else if(name[0] === "o" && name[1] === "n") {
		element[name] = value
	}
	else {
		element.setAttribute(name, value)
	}
}

const unsetProp = function(element, name)
{
	if(name === "class") {
		element.className = ""
	}
	else if(name === "style") {
		element.style.cssText = ""
	}
	else if(name[0] === "o" && name[1] === "n") {
		element[name] = null
	}
	else {
		element.removeAttribute(name)
	}
}

const render = function(component, parentElement)
{
	if(!bodyNode) {
		bodyNode = new VNode(0, "body", null, parentElement, null)
	}

	stackIndex = 0
	stack[0] = bodyNode

	componentVoid(component)

	bodyNode.index = 0
}

const renderInstance = function(instance)
{
	const vnode = instance.vnode
	const parentVNode = vnode.parent
	parentVNode.index = vnode.id

	stackIndex = instance.depth
	stack[instance.depth] = parentVNode

	instance.render()
	instance.dirty = false

	parentVNode.index = 0
}

const removeUnusedNodes = function(node)
{
	const children = node.children
	for(let n = node.index; n < children.length; n++) {
		const child = children[n]
		removeNode(child)
	}

	children.length = node.index
}

const removeNode = function(node)
{
	if(node.component) {
		node.component.remove()
	}

	node.children.length = 0
	node.element.parentElement.removeChild(node.element)

	const children = node.children
	for(let n = 0; n < children.length; n++) {
		const child = children[n]
		removeNode(child)
	}
}

const removeAll = function() {
	removeUnusedNodes(bodyNode)
}

export {
	elementOpen,
	elementClose,
	elementVoid,
	componentVoid,
	text,
	render,
	renderInstance,
	removeAll
}<|endoftext|>


<commit_before>angular.module("umbraco").controller("Imulus.ArchetypeConfigController", function ($scope, $http, assetsService, dialogService, archetypePropertyEditorResource) {

    //$scope.model.value = "";
    //console.log($scope.model.value);

    //define empty items
    var newPropertyModel = '{"alias": "", "remove": false, "collapse": false, "label": "", "helpText": "", "dataTypeGuid": "0cc0eba1-9960-42c9-bf9b-60e150b429ae", "value": ""}';
    var newFieldsetModel = '{"alias": "", "remove": false, "collapse": false, "labelTemplate": "", "icon": "", "label": "", "properties": [' + newPropertyModel + '], "group": null}';
    var defaultFieldsetConfigModel = JSON.parse('{"showAdvancedOptions": false, "startWithAddButton": false, "hideFieldsetToolbar": false, "enableMultipleFieldsets": false, "hideFieldsetControls": false, "hidePropertyLabel": false, "maxFieldsets": null, "enableCollapsing": true, "enableCloning": false, "enableDisabling": true, "enableDeepDatatypeRequests": false, "fieldsets": [' + newFieldsetModel + '], "fieldsetGroups": []}');

    //ini the model
    $scope.model.value = $scope.model.value || defaultFieldsetConfigModel;

    //ini the render model
    initConfigRenderModel();

    //get the available datatypes
    archetypePropertyEditorResource.getAllDataTypes().then(function(data) {
        $scope.availableDataTypes = data;
    });

    //iconPicker
    $scope.selectIcon = function(fieldset){
        var dialog = dialogService.iconPicker({
            callback: function(data){
                fieldset.icon = data;
            }
        });
    }

    //config for the sorting
    $scope.sortableOptions = {
        axis: 'y',
        cursor: "move",
        handle: ".handle",
        update: function (ev, ui) {

        },
        stop: function (ev, ui) {

        }
    };

    //function that determines how to manage expanding/collapsing fieldsets
    $scope.focusFieldset = function(fieldset){
        var iniState;

        if(fieldset)
        {
            iniState = fieldset.collapse;
        }

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
            if($scope.archetypeConfigRenderModel.fieldsets.length == 1 && fieldset.remove == false)
            {
                fieldset.collapse = false;
                return;
            }

            if(fieldset.label)
            {
                fieldset.collapse = true;
            }
            else
            {
                fieldset.collapse = false;
            }
        });

        if(iniState)
        {
            fieldset.collapse = !iniState;
        }
    }

    //ini the fieldsets
    $scope.focusFieldset();

    //function that determines how to manage expanding/collapsing properties
    $scope.focusProperty = function(properties, property){
        var iniState;

        if(property)
        {
            iniState = property.collapse;
        }

        _.each(properties, function(property){
            if(property.label)
            {
                property.collapse = true;
            }
            else
            {
                property.collapse = false;
            }
        });

        if(iniState)
        {
            property.collapse = !iniState;
        }
    }

    //ini the properties
    _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
        $scope.focusProperty(fieldset.properties);
    });

    //setup JSON.stringify helpers
    $scope.archetypeConfigRenderModel.toString = stringify;

    //encapsulate stringify (should be built into browsers, not sure of IE support)
    function stringify() {
        return JSON.stringify(this);
    }

    //watch for changes
    $scope.$watch('archetypeConfigRenderModel', function (v) {
        //console.log(v);
        if (typeof v === 'string') {
            $scope.archetypeConfigRenderModel = JSON.parse(v);
            $scope.archetypeConfigRenderModel.toString = stringify;
        }
    });

    $scope.autoPopulateAlias = function(s) {
        var modelType = s.hasOwnProperty('fieldset') ? 'fieldset' : 'property';
        var modelProperty = s[modelType];

        if (!modelProperty.aliasIsDirty) {
            modelProperty.alias = modelProperty.label.toUmbracoAlias();
        }
    }

    $scope.markAliasDirty = function(s) {
        var modelType = s.hasOwnProperty('fieldset') ? 'fieldset' : 'property';
        var modelProperty = s[modelType];

        if (!modelProperty.aliasIsDirty) {
            modelProperty.aliasIsDirty = true;;
        }
    }

    //helper that returns if an item can be removed
    $scope.canRemoveFieldset = function ()
    {
        return countVisibleFieldset() > 1;
    }

    //helper that returns if an item can be sorted
    $scope.canSortFieldset = function ()
    {
        return countVisibleFieldset() > 1;
    }

    //helper that returns if an item can be removed
    $scope.canRemoveProperty = function (fieldset)
    {
        return countVisibleProperty(fieldset) > 1;
    }

    //helper that returns if an item can be sorted
    $scope.canSortProperty = function (fieldset)
    {
        return countVisibleProperty(fieldset) > 1;
    }

    $scope.getDataTypeNameByGuid = function (guid) {
        if ($scope.availableDataTypes == null) // Might not be initialized yet?
            return "";

        var dataType = _.find($scope.availableDataTypes, function(d) {
            return d.guid == guid;
        });

        return dataType == null ? "" : dataType.name;
    }

    //helper to count what is visible
    function countVisibleFieldset()
    {
        var count = 0;

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
            if (fieldset.remove == false) {
                count++;
            }
        });

        return count;
    }

    //determines how many properties are visible
    function countVisibleProperty(fieldset)
    {
        var count = 0;

        for (var i in fieldset.properties) {
            if (fieldset.properties[i].remove == false) {
                count++;
            }
        }

        return count;
    }

    //handles a fieldset add
    $scope.addFieldsetRow = function ($index, $event) {
        $scope.archetypeConfigRenderModel.fieldsets.splice($index + 1, 0, JSON.parse(newFieldsetModel));
        $scope.focusFieldset();
    }

    //rather than splice the archetypeConfigRenderModel, we're hiding this and cleaning onFormSubmitting
    $scope.removeFieldsetRow = function ($index) {
        if ($scope.canRemoveFieldset()) {
            if (confirm('Are you sure you want to remove this?')) {
                $scope.archetypeConfigRenderModel.fieldsets[$index].remove = true;
            }
        }
    }

    //handles a property add
    $scope.addPropertyRow = function (fieldset, $index) {
        fieldset.properties.splice($index + 1, 0, JSON.parse(newPropertyModel));
    }

    //rather than splice the archetypeConfigRenderModel, we're hiding this and cleaning onFormSubmitting
    $scope.removePropertyRow = function (fieldset, $index) {
        if ($scope.canRemoveProperty(fieldset)) {
            if (confirm('Are you sure you want to remove this?')) {
                fieldset.properties[$index].remove = true;
            }
        }
    }

    //helper to ini the render model
    function initConfigRenderModel()
    {
        $scope.archetypeConfigRenderModel = $scope.model.value;
        if (!$scope.archetypeConfigRenderModel.fieldsetGroups) {
            $scope.archetypeConfigRenderModel.fieldsetGroups = [];
        }

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset) {

            if (fieldset.group) {
                // tie the fieldset group back up to the actual group object, not the clone that's been persisted
                fieldset.group = _.find($scope.archetypeConfigRenderModel.fieldsetGroups, function(fieldsetGroup) {
                    return fieldsetGroup.name == fieldset.group.name;
                })
            }

            fieldset.remove = false;
            if (fieldset.alias.length > 0)
                fieldset.aliasIsDirty = true;

            if(fieldset.label)
            {
                fieldset.collapse = true;
            }

            _.each(fieldset.properties, function(fieldset){
                fieldset.remove = false;
                if (fieldset.alias.length > 0)
                    fieldset.aliasIsDirty = true;
            });
        });
    }

    //sync things up on save
    $scope.$on("formSubmitting", function (ev, args) {
        syncModelToRenderModel();
    });

    //helper to sync the model to the renderModel
    function syncModelToRenderModel()
    {
        $scope.model.value = $scope.archetypeConfigRenderModel;
        var fieldsets = [];

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
            //check fieldsets
            if (!fieldset.remove) {
                fieldsets.push(fieldset);

                var properties = [];

                _.each(fieldset.properties, function(property){
                   if (!property.remove) {
                        properties.push(property);
                    }
                });

                fieldset.properties = properties;
            }
        });

        $scope.model.value.fieldsets = fieldsets;
    }

    $scope.showOptions = function ($event, template) {
        $event.preventDefault();

        dialogService.open({
            template: template,
            show: true,
            callback: function(data) {
                $scope.archetypeConfigRenderModel = data;
            },
            dialogData: $scope.archetypeConfigRenderModel
        });
    }

    //archetype css
    assetsService.loadCss("../App_Plugins/Archetype/css/archetype.css");
});
<commit_msg>Enforce one dialog at a time.<commit_after>angular.module("umbraco").controller("Imulus.ArchetypeConfigController", function ($scope, $http, assetsService, dialogService, archetypePropertyEditorResource) {

    //$scope.model.value = "";
    //console.log($scope.model.value);

    //define empty items
    var newPropertyModel = '{"alias": "", "remove": false, "collapse": false, "label": "", "helpText": "", "dataTypeGuid": "0cc0eba1-9960-42c9-bf9b-60e150b429ae", "value": ""}';
    var newFieldsetModel = '{"alias": "", "remove": false, "collapse": false, "labelTemplate": "", "icon": "", "label": "", "properties": [' + newPropertyModel + '], "group": null}';
    var defaultFieldsetConfigModel = JSON.parse('{"showAdvancedOptions": false, "startWithAddButton": false, "hideFieldsetToolbar": false, "enableMultipleFieldsets": false, "hideFieldsetControls": false, "hidePropertyLabel": false, "maxFieldsets": null, "enableCollapsing": true, "enableCloning": false, "enableDisabling": true, "enableDeepDatatypeRequests": false, "fieldsets": [' + newFieldsetModel + '], "fieldsetGroups": []}');

    //ini the model
    $scope.model.value = $scope.model.value || defaultFieldsetConfigModel;

    //ini the render model
    initConfigRenderModel();

    //get the available datatypes
    archetypePropertyEditorResource.getAllDataTypes().then(function(data) {
        $scope.availableDataTypes = data;
    });

    //iconPicker
    $scope.selectIcon = function(fieldset){
        var dialog = dialogService.iconPicker({
            callback: function(data){
                fieldset.icon = data;
            }
        });
    }

    //config for the sorting
    $scope.sortableOptions = {
        axis: 'y',
        cursor: "move",
        handle: ".handle",
        update: function (ev, ui) {

        },
        stop: function (ev, ui) {

        }
    };

    //function that determines how to manage expanding/collapsing fieldsets
    $scope.focusFieldset = function(fieldset){
        var iniState;

        if(fieldset)
        {
            iniState = fieldset.collapse;
        }

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
            if($scope.archetypeConfigRenderModel.fieldsets.length == 1 && fieldset.remove == false)
            {
                fieldset.collapse = false;
                return;
            }

            if(fieldset.label)
            {
                fieldset.collapse = true;
            }
            else
            {
                fieldset.collapse = false;
            }
        });

        if(iniState)
        {
            fieldset.collapse = !iniState;
        }
    }

    //ini the fieldsets
    $scope.focusFieldset();

    //function that determines how to manage expanding/collapsing properties
    $scope.focusProperty = function(properties, property){
        var iniState;

        if(property)
        {
            iniState = property.collapse;
        }

        _.each(properties, function(property){
            if(property.label)
            {
                property.collapse = true;
            }
            else
            {
                property.collapse = false;
            }
        });

        if(iniState)
        {
            property.collapse = !iniState;
        }
    }

    //ini the properties
    _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
        $scope.focusProperty(fieldset.properties);
    });

    //setup JSON.stringify helpers
    $scope.archetypeConfigRenderModel.toString = stringify;

    //encapsulate stringify (should be built into browsers, not sure of IE support)
    function stringify() {
        return JSON.stringify(this);
    }

    //watch for changes
    $scope.$watch('archetypeConfigRenderModel', function (v) {
        //console.log(v);
        if (typeof v === 'string') {
            $scope.archetypeConfigRenderModel = JSON.parse(v);
            $scope.archetypeConfigRenderModel.toString = stringify;
        }
    });

    $scope.autoPopulateAlias = function(s) {
        var modelType = s.hasOwnProperty('fieldset') ? 'fieldset' : 'property';
        var modelProperty = s[modelType];

        if (!modelProperty.aliasIsDirty) {
            modelProperty.alias = modelProperty.label.toUmbracoAlias();
        }
    }

    $scope.markAliasDirty = function(s) {
        var modelType = s.hasOwnProperty('fieldset') ? 'fieldset' : 'property';
        var modelProperty = s[modelType];

        if (!modelProperty.aliasIsDirty) {
            modelProperty.aliasIsDirty = true;;
        }
    }

    //helper that returns if an item can be removed
    $scope.canRemoveFieldset = function ()
    {
        return countVisibleFieldset() > 1;
    }

    //helper that returns if an item can be sorted
    $scope.canSortFieldset = function ()
    {
        return countVisibleFieldset() > 1;
    }

    //helper that returns if an item can be removed
    $scope.canRemoveProperty = function (fieldset)
    {
        return countVisibleProperty(fieldset) > 1;
    }

    //helper that returns if an item can be sorted
    $scope.canSortProperty = function (fieldset)
    {
        return countVisibleProperty(fieldset) > 1;
    }

    $scope.getDataTypeNameByGuid = function (guid) {
        if ($scope.availableDataTypes == null) // Might not be initialized yet?
            return "";

        var dataType = _.find($scope.availableDataTypes, function(d) {
            return d.guid == guid;
        });

        return dataType == null ? "" : dataType.name;
    }

    //helper to count what is visible
    function countVisibleFieldset()
    {
        var count = 0;

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
            if (fieldset.remove == false) {
                count++;
            }
        });

        return count;
    }

    //determines how many properties are visible
    function countVisibleProperty(fieldset)
    {
        var count = 0;

        for (var i in fieldset.properties) {
            if (fieldset.properties[i].remove == false) {
                count++;
            }
        }

        return count;
    }

    //handles a fieldset add
    $scope.addFieldsetRow = function ($index, $event) {
        $scope.archetypeConfigRenderModel.fieldsets.splice($index + 1, 0, JSON.parse(newFieldsetModel));
        $scope.focusFieldset();
    }

    //rather than splice the archetypeConfigRenderModel, we're hiding this and cleaning onFormSubmitting
    $scope.removeFieldsetRow = function ($index) {
        if ($scope.canRemoveFieldset()) {
            if (confirm('Are you sure you want to remove this?')) {
                $scope.archetypeConfigRenderModel.fieldsets[$index].remove = true;
            }
        }
    }

    //handles a property add
    $scope.addPropertyRow = function (fieldset, $index) {
        fieldset.properties.splice($index + 1, 0, JSON.parse(newPropertyModel));
    }

    //rather than splice the archetypeConfigRenderModel, we're hiding this and cleaning onFormSubmitting
    $scope.removePropertyRow = function (fieldset, $index) {
        if ($scope.canRemoveProperty(fieldset)) {
            if (confirm('Are you sure you want to remove this?')) {
                fieldset.properties[$index].remove = true;
            }
        }
    }

    //helper to ini the render model
    function initConfigRenderModel()
    {
        $scope.archetypeConfigRenderModel = $scope.model.value;
        if (!$scope.archetypeConfigRenderModel.fieldsetGroups) {
            $scope.archetypeConfigRenderModel.fieldsetGroups = [];
        }

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset) {

            if (fieldset.group) {
                // tie the fieldset group back up to the actual group object, not the clone that's been persisted
                fieldset.group = _.find($scope.archetypeConfigRenderModel.fieldsetGroups, function(fieldsetGroup) {
                    return fieldsetGroup.name == fieldset.group.name;
                })
            }

            fieldset.remove = false;
            if (fieldset.alias.length > 0)
                fieldset.aliasIsDirty = true;

            if(fieldset.label)
            {
                fieldset.collapse = true;
            }

            _.each(fieldset.properties, function(fieldset){
                fieldset.remove = false;
                if (fieldset.alias.length > 0)
                    fieldset.aliasIsDirty = true;
            });
        });
    }

    //sync things up on save
    $scope.$on("formSubmitting", function (ev, args) {
        syncModelToRenderModel();
    });

    //helper to sync the model to the renderModel
    function syncModelToRenderModel()
    {
        $scope.model.value = $scope.archetypeConfigRenderModel;
        var fieldsets = [];

        _.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
            //check fieldsets
            if (!fieldset.remove) {
                fieldsets.push(fieldset);

                var properties = [];

                _.each(fieldset.properties, function(property){
                   if (!property.remove) {
                        properties.push(property);
                    }
                });

                fieldset.properties = properties;
            }
        });

        $scope.model.value.fieldsets = fieldsets;
    }

    $scope.showOptions = function ($event, template) {
        $event.preventDefault();

        dialogService.closeAll();

        dialogService.open({
            template: template,
            show: true,
            callback: function(data) {
                $scope.archetypeConfigRenderModel = data;
            },
            dialogData: $scope.archetypeConfigRenderModel
        });
    }

    //archetype css
    assetsService.loadCss("../App_Plugins/Archetype/css/archetype.css");
});
<|endoftext|>