Pop-Up in Meilix Generator

Meilix Generator has fields which the user needs to fill up. There are fields for the required information and for customization process too. This solves the problems and helping user to know about the requirement of each field. We implemented a pop-menu which appears and tell about that particular field.

We will here describe only the implementation of email pop-up.

type="text/javascript">  
function hideDiv(){
    document.getElementsByClassName('custom-menu-cont')[0].classList.toggle('hidden')
}
document.addEventListener("click", hideDiv);
function noti()
{
	alert("Email is used to mail user the link to the build ISO");
}

We have embedded a javascript in html file.
Function hideDiv contains document.getElementsByClassName() method.

The getElementsByClassName() method returns a collection of all elements in the document with the specified class name.

Then we have a document.addEventListener() method with click and hideDiv function as the parameters. It also contain a function noti which get toggle at the time of click and display the alert message. hideDiv closes the toggle when clicked on the cross button again.

<label class="heading" id="label" for="email">Email *&ensp;<img src="{{ url_for('static', filename='alert.jpg') }}" height="20" width="20" onclick="noti()"></label>

 

We implemented a question mark after the email line which when clicked showed the following message on the webapp.

This will help user to know the requirement of the particular field.

Through the help of the required function and implementation of the button, we can embedded a help icon which pop-up to give required option present in the webapp.

These are different pop-ups that are currently present in the webapp. User can know about the particular field by clicking on any of the pop-up.

Resources

  1. HTML getElementsByClassName(): https://www.w3schools.com/jsref/met_document_getelementsbyclassname.asp
  2. Bootstrap 4 Form: https://www.w3schools.com/bootstrap4/bootstrap_forms.asp

Continue Reading

Adding a Countdown to Orders Page in Open Event Frontend

This blog post will illustrate how you can add a countdown to orders page which on finishing expires the ticket in Open Event. In Open Event we allow some predefined time for users to fill in their details and once the time expires order gets expired and tickets get released. Users can order the tickets again if they want.

We start by adding a createdAt field to orders model so that we can keep track of remaining time. To calculate the time when the order should expire we add predefined time in which user should fill their details to createdAt time. In this way, we get the time when the order will expire.

So now to calculate the remaining time we just subtract the expiring time from current time. And then we render this data into the template. We define getRemainingTime property in our template and fetch the data for that property with help of javascript.

To see the template code visit this link.

The challenge here is to update the time remaining after every second. For this, we take the help of ember runloop. The run.later() function of ember runloop helps us to calculate the property after every second and set it. Code for setting the remaining time with the help of javascript is given below.

// app/components/forms/orders/order-form.js

getRemainingTime: computed('data', function() {
    let willExpireAt = this.get('data.createdAt').add(10, 'minutes');
    this.timer(willExpireAt, this.get('data.identifier'));
  }),

  timer(willExpireAt, orderIdentifier) {
    run.later(() => {
      let currentTime = moment();
      let diff = moment.duration(willExpireAt.diff(currentTime));
      this.set('getRemainingTime', moment.utc(diff.asMilliseconds()).format('mm:ss'));
      if (diff > 0) {
        this.timer(willExpireAt, orderIdentifier);
      } else {
        this.get('data').reload();
        this.get('router').transitionTo('orders.expired', orderIdentifier);
      }
    }, 1000);
  }

 

As given in the code. We pass expiring time and order’s model instance to the timer function. Timer function calculates the remaining time and sets it to getRemainingTime property of template. Timer function runs after every second with the help of run.later() function of ember runloop. To format the remaining time into MM:SS we take help of moment.js library and format the data accordingly.

Once the remaining time is less than zero (time expires) we reload the model and transition current route to expired route. We do not have to set order status as expired inside the FE. Server sets the order as expired after the predefined time. So we just reload the model from the server and we get the updated status of the order.

Resources:
Continue Reading

Implementing Scheduler Actions on Open Event Frontend

After the functionality to display scheduled sessions was added to Open Event Frontend, the read-only implementation of the scheduler had been completed. What was remaining now in the scheduler were the write actions, i.e., the sessions’ scheduling which event organizers do by deciding its timings, duration and venue.

First of all, these actions required the editable flag to be true for the fullcalendar plugin. This allowed the sessions displayed to be dragged and dropped. Once this was enabled, the next task was to embed data in each of the unscheduled sessions so that when they get dropped on the fullcalendar space, they get recognized by the calendar, which can place it at the appropriate location. For this functionality, they had to be jQuery UI draggables and contain an “event” data within them. This was accomplished by the following code:

this.$().draggable({
  zIndex         : 999,
  revert         : true,      // will cause the event to go back to its
  revertDuration : 0  //  original position after the drag
});

this.$().data('event', {
  title    : this.$().text().replace(/\s\s+/g, ' '), // use the element's text as the event title
  id       : this.$().attr('id'),
  serverId : this.get('session.id'),
  stick    : true, // maintain when user navigates (see docs on the renderEvent method)
  color    : this.get('session.track.color')
});

Here, “this” refers to each unscheduled session. Note that the session color is fetched via the corresponding session track. Once the unscheduled sessions contain enough relevant data and are of the right type (i.e, jQuery UI draggable type), they’re ready to be dropped on the fullcalendar space.

Now, when an unscheduled session is dropped on the fullcalendar space, fullcalendar’s eventReceive callback is triggered after its drop callback. In this callback, the code removes the session data from the unscheduled sessions’ list, so it disappears from there and gets stuck to the fullcalendar space. Then the code in the drop callback makes a PATCH request to Open Event Server with the relevant data, i.e, start and end times as well as microlocation. This updates the corresponding session on the server.

Similarly, another callback is generated when an event is resized, which means when its duration is changed. This again sends a corresponding session PATCH request to the server. Furthermore, the functionality to pop a scheduled event out of the calendar and add it back to the unscheduled sessions’ list is also implemented, just like in Eventyay version 1. For this, a cross button is implemented, which is embedded in each scheduled session. Clicking this pops the session out of the calendar and adds it back to the unscheduled sessions list. Again, a corresponding PATCH request is sent to the server.

After getting the response of such requests, a notification is displayed on the screen, which informs the users whether the action was successful or not. The main PATCH functionality is in a separate function which is called by different callbacks accordingly, so code reusability is increased:

updateSession(start, end, microlocationId, sessionId) {
    let payload = {
      data: {
        attributes: {
          'starts-at' : start ? start.toISOString() : null,
          'ends-at'   : end ? end.toISOString() : null
        },
        relationships: {
          microlocation: {
            data: {
              type : 'microlocation',
              id   : microlocationId
            }
          }
        },
        type : 'session',
        id   : sessionId
      }
    };

    let config = {
      skipDataTransform: true
    };
    return this.get('loader')
      .patch(`sessions/${sessionId}`, JSON.stringify(payload), config)
      .then(() => {
        this.get('notify').success('Changes have been made successfully');
      })
      .catch(reason => {
        this.set('error', reason);
        this.get('notify').error(`Error: ${reason}`);
      });
  },

This completes the scheduler implementation on Open Event Frontend. Here is how it looks in action:

scheduler actions.gif

Resources

Continue Reading

Implementing Scheduled Sessions in Open Event Scheduler

Until recently, the Open Event Frontend version 2 didn’t have the functionality to display the already scheduled sessions of an event on the sessions scheduler. Displaying the already scheduled sessions is important so that the event organizer can always use the sessions scheduler as a draft and not worry about losing progress or data about scheduled sessions’ timings. Therefore, just like a list of unscheduled sessions was implemented for the scheduler, the provision for displaying scheduled sessions also had to be implemented.

The first step towards implementing this was to fetch the scheduled sessions’ details from Open Event Server. To perform this fetch, an appropriate filter was required. This filter should ideally ask the server to send only those sessions that are “scheduled”. Thus, scheduled sessions need to be defined as sessions which have a non-null value of its starts-at and ends-at fields. Also, few more details are required to be fetched for a clean display of scheduled sessions. First, the sessions’ speaker details should be included so that the speakers’ names can be displayed alongside the sessions. Also, the microlocations’ details need to be included so that each session is displayed according to its microlocation. For example, if a session is to be delivered in a place named ‘Lecture Hall A’, it should appear under the ‘Lecture Hall A’ microlocation column. Therefore, the filter goes as follows:

let scheduledFilterOptions = [
      {
        and: [
          {
            name : 'starts-at',
            op   : 'ne',
            val  : null
          },
          {
            name : 'ends-at',
            op   : 'ne',
            val  : null
          }
        ]
      }
    ];

 

After fetching the scheduled sessions’ details, they need to be delivered to the fulllcalendar code for displaying on the session scheduler. For that, the sessions need to be converted in a format which can be parsed by the fullcalendar add-on of emberJS. For example, fullcalendar calls microlocations as ‘resources’. Here is the format which fullcalendar understands:

{
        title      : `${session.title} | ${speakerNames.join(', ')}`,
        start      : session.startsAt.format('YYYY-MM-DDTHH:mm:SS'),
        end        : session.endsAt.format('YYYY-MM-DDTHH:mm:SS'),
        resourceId : session.microlocation.get('id'),
        color      : session.track.get('color'),
        serverId   : session.get('id') // id of the session on BE
}

 

Once the sessions are in the appropriate format, their data is sent to the fullcalendar template, which renders them on the screen:

Screen Shot 2018-08-21 at 8.20.27 PM.png

This completes the implementation of displaying the scheduled sessions of an event on the Open Event Scheduler.

Resources

Continue Reading

Implementing Unscheduled Sessions List for Event Scheduler

Until recently, Open Event Server didn’t allow the storage of unscheduled sessions. However, having the provision of unscheduled sessions was necessary so that event organizers can easily schedule the unscheduled sessions and keep track of them. Also, it allows them to remove scheduled sessions from the scheduler and place them in the unscheduled sessions list, so that they can be scheduled later. Also, since the unscheduled sessions list was also present in Eventyay version 1, it was decided to have the same in version 2.

The first step was to enable the storage of unscheduled sessions on the server. For this, the starts-at and ends-at fields of the session model were modified to be non-required (earlier they were mandatory). Once this change was done, the next step was to fetch the list of unscheduled sessions on the frontend, from the server. Unscheduled sessions were the ones which had the starts-at and ends-at fields as null. Also, the speakers’ details needed to be fetched so that their names can be mentioned along with sessions’ titles, in accordance with Eventyay version 1. Thus, the following were the filter options for the unscheduled sessions’ fetching:

let unscheduledFilterOptions = [
      {
        and: [
          {
            name : 'starts-at',
            op   : 'eq',
            val  : null
          },
          {
            name : 'ends-at',
            op   : 'eq',
            val  : null
          }
        ]
      }
];
 
let unscheduledSessions = await eventDetails.query('sessions', {
      include : 'speakers,track',
      filter  : unscheduledFilterOptions
    });

 

This gave us the list of unscheduled sessions on the frontend appropriately. After this, the next step was to display this list to the event organizer. For this, the scheduler’s Handlebars template file was modified appropriately. The colors and sizes were chosen so that the list looks similar to the one in Eventyay version 1. Also, the Ember add-on named ember-drag-drop was used to make these unscheduled session components draggable, so that they can be ultimately scheduled on the scheduler. After installing this package and making the necessary changes to the project’s package.json file, the component file for unscheduled sessions was modified accordingly to adapt for the draggable components’ UI. This was the final step and completed the implementation of listing unscheduled sessions.

unscheduled_sessions.gif

Resources

Continue Reading

Open Event Frontend – Events Explore Page

This blog illustrates how the events explore page is implemented in the Open Event Frontend web app. The user can land on the events explore page by clicking on Browse Events button in the top panel on the home page, shown by the mouse tip in the following picture.

Here, the user can use the various filter options provided to search for the events as per his requirements, He/she can filter according to categories, sub-categories for each category, event type, and date range. A unique feature here is that the user can pick from the start date range options such as today, tomorrow, this week, this weekend, next week and many more. If neither of these fits his needs he can use custom dates as well. The user can also filter events using event location which is autocompleted using Google Maps API. Thus, searching for events is fast, easy and fun.

Let us see how this has been implemented.

Implementation

The explore routes has a method _loadEvents(params). Here, params is the various query parameters for filtering the events. This method forms the query, sends it to the server and returns the list of events returned by the server. The server uses Flask-REST-JSONAPI. It has a very flexible filtering system. It is completely related to the data layer used by the ResourceList manager. More information about this can be found here.

So, the filters are formed using syntax specified in the link mentioned above. We form an array filterOptions which stores the various filters. The default filter is that the event should be published:

let filterOptions = [
 {
   name : 'state',
   op  : 'eq',
   val  : 'published'
 }
];

Then we check for each filter option and check if it is present or not. If yes then we add it to filterOptions. An example as follows:

if (params.category) {
 filterOptions.push({
   name : 'event-topic',
   op  : 'has',
   val  : {
     name : 'name',
     op : 'eq',
     val : params.category
   }
 });
}

This is repeated for sub_category, event_type, location and start_date and end_date. An event is considered to fulfill the date filter if it satisfies any one of the given conditions:

  • If both start_date and end_date are mentioned:
    • Event start_date is after filter start date and before filter end date.
    • Or, event end date if after filter start date and before filter end date.
    • Or, event start date is before filter start date and event end date date is after filter end date.
  • If only start_date is mentioned, then if the event start date is after filter start date or event end date is after filter start date.

The code to this can be found here. For the date ranges mentioned above(today, tomorrow etc) the start dates and end dates are calculated using the moment.js library and then passed on as params.

The filteredEvents are passed in the route model.

async model(params) {
 return {
   eventTypes     : await this.store.findAll('event-type'),
   eventTopics    : await this.store.findAll('event-topic', { include: 'event-sub-topics' }),
   filteredEvents : await this._loadEvents(params)
 };
}

The variable is set in the controller and any change to the query params is observed for. The method _loadEvents is called whenever the query params change.

setupController(controller, model) {
 this._super(...arguments);
 controller.set('filteredEvents', model.filteredEvents);
 this.set('controller', controller);
},

actions: {
 async queryParamsDidChange(change, params) {
   if (this.get('controller')) {
     this.get('controller').set('filteredEvents', await this._loadEvents(params));
   }
 }
}

The template iterates over the filteredEvents and displays each one in a card.

Resources

Continue Reading

Implementing User Email Verification in Open Event Frontend

Open Event Server provides the functionality of user email verification after a user registers, but it was not implemented on Open Event Frontend until recently. For users, this meant they were still not able to verify themselves, even after receiving confirmation links in their inboxes, which were sent by the server. Thus, implementing it on frontend was crucial for a complete user registration workflow.

Since the server had already exposed an endpoint to perform the registration, all that was required on the frontend to be done was to make a call to this endpoint with the necessary data. The entire process can be summarized as follows:

  1. The recently registered user clicks on the verification link she receives on her email
  2. The above step opens the link, which is of the format http://fossasia.github.io/open-event-frontend/verify?token=
  3. As soon as the frontend server receives this request, it extracts the token from the URL query parameter
  4. The token is now sent to the backend server as a patch request
  5. The response of the above request confirms whether the user verification is successful or not, and an appropriate message is displayed

In the frontend code, the above algorithm is spread across 3 files: the router, verify route and verify controller. A new route named /verify was implemented for the user verification, and was registered in the project’s main router.js file. After that, in the verify route, the beforeModel() method is used to trigger the above algorithm before the page is loaded:

// in app/routes/verify.js

beforeModel(transition) {
this.controllerFor('verify').verify(transition.queryParams.token);
}

The main algorithm above is implemented in the verify controller:

// in app/controllers/verify.js
...
queryParams : ['token'],
token       : null,
success     : false,
error       : null,

verify(tokenVal) {
let payload = {
data: {
token: tokenVal
}
};
return this.get('loader')
.post('auth/verify-email', payload)
.then(() => {
this.set('success', true);
})
.catch(reason => {
this.set('error', reason);
this.set('success', false);
});
}
});

 

A template for displaying the success or failure messages to the user was also created. It uses the value of the success boolean set above to decide the message to be displayed to the user. The user registration workflow is now complete and the user sees the following message after clicking on the verification link she receives:

Screen Shot 2018-08-21 at 7.10.43 PM

Resources

Continue Reading

Forgot Password Service in Badgeyay

Badgeyay is an open source badge generator service for generating badges developed by FOSSASIA community for technical events and conferences. The project is divided into two components mainly frontend and backend. After creating the user registration functionality in application, if the user forgets the credentials for the login, then there must be a way to recreate the credentials using a secure channel. This is only valid for the users signed up through email login as for the case of OAuth they must have access to their ID on respective social platform. The main challenges in resetting password for the user is to provide a secure channel. So the problem can be breakdown into following issues:

  • Creating a token for reset action
  • Sending that token via mail to user
  • Verifying that token on the server and giving access
  • Changing the credentials  of the user

Procedure

  1. Generating token for the request to change credentials for the user. The token will be an expiry token and will be expired in the mentioned duration. So the token is valid for only a limited period of time and will prevent fraudulent requests.
def pwd_reset_token():
  data = request.get_json()[‘data’][‘attributes’]
  if ’email’ not in data.keys():
      print(‘Email not found’)
  email = data[’email’]
  user = User.getUser(email=email)
  if not user:
      return ErrorResponse(UserNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()
  expire = datetime.datetime.utcnow() + datetime.timedelta(seconds=900)
  token = jwt.encode({
      ‘id’: user.id,
      ‘exp’: expire
  }, app.config.get(‘SECRET_KEY’))
  resetObj = ResetPasswordToken(user.id, token.decode(‘UTF-8’))
  resetObj.save_to_db()
  return jsonify(TokenSchema().dump(resetObj).data)

Model for ResetPasswordToken

class ResetPasswordToken(db.Model):

  __tablename__ = ‘Reset Password Token’

  id = db.Column(db.String, primary_key=True)
  token = db.Column(db.String, nullable=False)

  def __init__(self, uid, token):
      self.id = uid
      self.token = token

  def save_to_db(self):
      try:
          db.session.add(self)
          db.session.commit()
      except Exception as e:
          db.session.rollback()
          db.session.flush()
          print(e)

 

  1. Sending the password reset link via mail to the user. The link will contain the token (expiry token) that will be used to validate the request. For the case we will be using Firebase Cloud functions as an HTTP Trigger.
exports.sendResetMail = functions.https.onRequest((req, res) => {
let token = req.query[‘token’];
let email = req.query[’email’];
res.setHeader(‘Content-Type’, ‘application/json’);
sendResetMail(token, email)
  .then(() => {
    console.log(‘Reset mail sent to’, email);
    res.json({ data: { attributes: { status: 200 }, id: token, type: ‘reset-mails’ } });
    return 0;
  })
  .catch(err => {
    console.error(err);
    res.json({ data: { attributes: { status: 500 }, id: token, type: ‘reset-mails’ } });
    return -1;
  });
});

function sendResetMail(token, email) {
const mailOptions = {
  from: `${APP_NAME}<[email protected]>`,
  to: email,
};

mailOptions.subject = `Password reset link`;
mailOptions.html = ‘<p>Hey ‘ + email + ‘! Here is your password reset <a href=\” + PASSWORD_RESET_LINK
  + token + ‘\’>Link</a><p>’;
return mailTransport.sendMail(mailOptions);
}

 

  1. Verifying the token on the server side to validate the user request
def validate_reset_token():
  args = request.args
  if ‘token’ in args.keys():
      token = args.get(‘token’)
  resp = {‘id’: token}
  try:
      jwt.decode(token, app.config[‘SECRET_KEY’])
      resp[‘valid’] = True
      return jsonify(ValidTokenSchema().dump(resp).data)
  except Exception as e:
      resp[‘valid’] = False
      print(e)
      return jsonify(ValidTokenSchema().dump(resp).data)

 

  1. After user has access to change the credentials, then user can send a POST request to backend through a form shown in UI to change its password.
def changePwd():
  try:
      data = request.get_json()[‘data’][‘attributes’]
  except Exception as e:
      print(e)
      return ErrorResponse(PayloadNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  token = data[‘token’]
  try:
      decoded_res = jwt.decode(token, app.config[‘SECRET_KEY’])
  except Exception as e:
      print(e)
      return ErrorResponse(SignatureExpired().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  user = User.getUser(user_id=decoded_res[‘id’])

  if ‘pwd’ not in data.keys():
      return ErrorResponse(PasswordNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  pwd = data[‘pwd’]
  oldPwd = user.password
  user.password = generate_password_hash(pwd)
  user.save_to_db()

  resp = {‘id’: token}
  if update_firebase_password(user.id, pwd):
      resp[‘status’] = ‘Changed’
      return jsonify(ResetPasswordOperation().dump(resp).data)
  else:
      print(‘Firebase not uploaded’)
      user.password = oldPwd
      user.save_to_db()
      resp[‘status’] = ‘Not Changed’
      return jsonify(ResetPasswordOperation().dump(resp).data)

 

  1. After this the password of the user will be changed and allowed to login through new credentials.

Link to PRs:

  • PR for forgot password reset form – #1
  • PR for implementing forgot password on firebase side – #2
  • PR for password reset mail functionality – #3

Resources

  • HTTP Trigger Cloud functions – Link
  • Nodemailer message configuration – Link
  • Ember Data Guide – Link

 

Continue Reading

Ember Controller for Badge Generation In Badgeyay

Badgeyay is an open source project developed by FOSSASIA Community. This project aims towards giving a platform for badge generation using several customizations options. Current structure of project is in two parts to maintain modularity, which are namely backend, developed in flask, and frontend, developed in ember.

After refactoring the frontend and backend API we need to create a controller for the badge generation in frontend. Controller will help the components to send and receive data from them and prepare the logic for sending request to API so that badges can be generated and can receive the result as response from the server. Particularly we need to create the controller for badge generation route, create-badges.

As there are many customizations option presented to user, we need to chain the requests so that they sync with each other and the logic should not break for the badge generation.

Procedure

  1. Creating the controller from the ember-cli
ember g controller create-badge

 

  1. After the component generation, we need to create actions that can be passed to components. Let’s build action to submit form and then chain the different actions together for the badge generation.
submitForm() {
  const _this = this;
  const user = _this.get(‘store’).peekAll(‘user’);
  let uid;
  user.forEach(user_ => {
    uid = user_.get(‘id’);
  });
  if (uid !== undefined && uid !== ) {
    _this.set(‘uid’, uid);
  }

  let badgeData = {
    uid     : _this.uid,
    badge_size : ‘A3’
  };

  if (_this.csvEnable) {
    badgeData.csv = _this.csvFile;
  }
  if (_this.defFontColor !== && _this.defFontColor !== undefined) {
    badgeData.font_color = ‘#’ + _this.defFontColor;
  }
  if (_this.defFontSize !== && _this.defFontSize !== undefined) {
    badgeData.font_size = _this.defFontSize.toString();
  }
  if (_this.defFont !== && _this.defFont !== undefined) {
    badgeData.font_type = _this.defFont;
  }

  _this.send(‘sendManualData’, badgeData);

},

 

  1. As we can see in the above code snippet that _this.send(action_name, arguments) is calling another action sendManualData. This action then sends a network request to the backend if the Manual data is selected as input source otherwise will go with the CSV upload. If no option is chosen then it will show an error on the user screen, notifying him to select one input source.
sendManualData(badgeData) {
    const _this = this;
    if (_this.manualEnable) {
      let textEntry = _this.get(‘store’).createRecord(‘text-data’, {
        uid   : _this.uid,
        manual_data : _this.get(‘textData’),
        time   : new Date()
      });
      textEntry.save().then(record => {
        _this.set(‘csvFile’, record.filename);
        badgeData.csv = _this.csvFile;
        _this.send(‘sendDefaultImg’, badgeData);
        _this.get(‘notify’).success(‘Text saved Successfully’);
      }).catch(err => {
        let userErrors = textEntry.get(‘errors.user’);
        if (userErrors !== undefined) {
          _this.set(‘userError’, userErrors);
        }
      });
    } else if (_this.csvEnable) {
      if (_this.csvFile !== undefined && _this.csvFile !== ) {
        badgeData.csv = _this.csvFile;
        _this.send(‘sendDefaultImg’, badgeData);
      }
    } else {
      // No Input Source specified Error
    }
  },

 

The above code will choose the manual data if the manual data boolean flag is set else not, and then does a network request and wait for the promise to be resolved. As soon as the promise is resolved it calls another action to for the default image.

  1. After selecting the input source, now the background for the badge has to be selected. It will look for the boolean flags of the defaultImage, backgroundColorImage, customImage and will make the network request accordingly.
sendDefaultImg(badgeData) {
    const _this = this;
    if (_this.defImage) {
      let imageRecord = _this.get(‘store’).createRecord(‘def-image-upload’, {
        uid    : _this.uid,
        defaultImage : _this.defImageName
      });
      imageRecord.save()
        .then(record => {
          _this.set(‘custImgFile’, record.filename);
          badgeData.image = _this.custImgFile;
          _this.send(‘sendBadge’, badgeData);
        })
        .catch(error => {
          let userErrors = imageRecord.get(‘errors.user’);
          if (userErrors !== undefined) {
            _this.set(‘userError’, userErrors);
          }
        });
    } else if (_this.custImage) {
      if (_this.custImgFile !== undefined && _this.custImgFile !== ) {
        badgeData.image = _this.custImgFile;
        _this.send(‘sendBadge’, badgeData);
      }
    } else if (_this.colorImage && _this.defColor !== undefined && _this.defColor !== ) {
      console.log(_this.defColor);
      let imageRecord = _this.get(‘store’).createRecord(‘bg-color’, {
        uid : _this.uid,
        bg_color : _this.defColor
      });
      imageRecord.save()
        .then(record => {
          badgeData.image = record.filename;
          _this.send(‘sendBadge’, badgeData);
        })
        .catch(error => {
          let userErrors = imageRecord.get(‘errors.user’);
          if (userErrors !== undefined) {
            _this.set(‘userError’, userErrors);
          }
        });
    } else {
      // Inflate error for No Image source.
    }
  },

 

After the promise resolvement, the final action is called to send badge data payload to backend api for badge generation.

  1. After the complete preparation of the payload, now it’s time to send the payload to the backend api for the badge generation and after the promise resolvement showing the respective downloadable link in the frontend.
sendBadge(badgeData) {
    const _this = this;
    let badgeRecord = _this.get(‘store’).createRecord(‘badge’, badgeData);
    badgeRecord.save()
      .then(record => {
        _this.set(‘badgeGenerated’, true);
        _this.set(‘genBadge’, record.id);
        _this.get(‘notify’).success(‘Badge generated Successfully’);
      })
      .catch(err => {
        console.error(err.message);
      });
  },

 

Now after the promise resolvement the local variable badgGenerated is set to true so that the success message can be shown in the frontend for successful badge generation along with the link.

Link to respective PR – Link

Topics Involved

  • Chaining of actions and requests
  • Manipulating DOM on the conditional statements
  • Component bindings
  • Ember data
  • Promise resolvement

Resources

  • Link to ember data for the API requests and promise resolvement – Link
  • Implementing Controllers in Ember – Link
  • Chaining actions together in ember – Link
Continue Reading

Adding helper for Default Images

A Badge generator like Badgeyay must be able to generate, store and export the user data as and when needed. This blog post covers the addition of a helper function for the default images in the badge generator. We cover the topics such as the problem caused and the solution to the problem.

The problem was that the names of default images were not being rendered properly.

Adding the functionality to badgeyay

Let us see how we implemented this functionality into the frontend of the project.

Step 1 : Generating helper function

We first need to generate the helper function in order to get it started. Generating a helper function is not a tough task. We need to follow the steps as mentioned below

$ ~ ember generate helper def-images

After this command is executed in the terminal, ember-cli generates two files, the helper file and the helper-test file.

Step 2 : Adding the functionality to helper function

After generating the helper file, we need to add relevant code in order to make sure that the above stated problem is solved. So we work on the helper function to recreate the formatted names from the incoming unformatted names from ember model.

import { helper } from ‘@ember/component/helper’;

export function defImages(params) {
var [imageName] = params;
imageName = imageName.split(
‘_’);
for (var i = imageName.length – 1; i >= 0; i–) {
imageName[i] = imageName[i].charAt(
0).toUpperCase() + imageName[i].slice(1);
}
return imageName.join(‘ ‘);
}

export default helper(defImages);

Step 3 : Adding test to the helper-test.js

Once we have the helper set up correctly, we can now add a test for the same.

import { module, test } from ‘qunit’;
import { setupRenderingTest } from ’ember-qunit’;
import { render } from ‘@ember/test-helpers’;
import hbs from ‘htmlbars-inline-precompile’;

module(‘Integration | Helper | defImages’, function(hooks) {
setupRenderingTest(hooks);

// Replace this with your real tests.
test(
‘it renders’, async function(assert) {
this.set(‘inputValue’, ‘fossasia_badgeyay’);

await render(hbs`{{def-images inputValue}}`);

assert.equal(this.element.textContent.trim(), ‘Fossasia Badgeyay’);
});
});

Finally, we need to add the helper function to the frontend component as well.

<div class=“twelve wide column”>{{def-images image.name}}</div>

Now, the names of the individual default images are being formatted correctly.

Screenshot of changes

Resources

Continue Reading
Close Menu
%d bloggers like this: