Skip to content

Instantly share code, notes, and snippets.

@ANRCorleone
Last active January 29, 2017 21:31
Show Gist options
  • Save ANRCorleone/3d56a6e58632d739d936cdd211d1e2e9 to your computer and use it in GitHub Desktop.
Save ANRCorleone/3d56a6e58632d739d936cdd211d1e2e9 to your computer and use it in GitHub Desktop.
Some of my javascript code snippets from our team project - Property.Community
//Big thank you to Ryan Niemeyer for sharing his wisdom on KnockoutJS
//Learned it quickly because of him.
//There are others I forgot, he just stood out to me as a KnockoutJS guru :)
//Our Generic and reusable Javascripts should go here.
var ourProject = ourProject || (ourProject = {});
(function (project) {
//KnockoutJS with bootstrap modal. make sure you have an observable named pickedTemplate on your $root context. You will
//write your template name on that observable.
//The data passed to the template is the valueAccessor you pass to this custom binding.
ko.bindingHandlers.showModal = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var modal = valueAccessor();
//initialize modal, then clear the observable when the modal closes
$(element).modal({ show: false, backdrop: 'static' })
.on("hidden.bs.modal", function () {
if (ko.isWriteableObservable(modal)) {
modal(null);
}
});
var templateName = function () {
var value = bindingContext.$root.pickedTemplate();
console.log(value);
return value;
};
var templateData = ko.computed({
read: function () {
console.log(modal);
return modal;
},
disposeWhenNodeIsRemoved: element
});
return ko.applyBindingsToNode(element, { template: { if: modal, name: templateName, data: templateData } }, bindingContext);
},
update: function (elem, valueAccessor) {
var value = ko.unwrap(valueAccessor());
console.log(value);
$(elem).modal(value ? "show" : "hide");
}
};
//Google Autocomplete with KnockoutJS..Just bind to addressAutocomplete :)
ko.bindingHandlers.addressAutocomplete = {
init: function(element,boundProp,allProp,data,context) {
var allBindings = ko.unwrap(allProp());
var autocomplete;
var options = {};
var geocoder = new google.maps.Geocoder();
var nzBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-48.8809,174.9462),
new google.maps.LatLng(-34.3275,162.7294));
var componentForm = {
street_number: 'short_name',
route: 'long_name',
sublocality_level_1: 'short_name',
locality: 'long_name',
postal_code: 'short_name'
};
var addressForm = {
street_number: 'Number',
route: 'Street',
sublocality_level_1: 'Suburb',
locality: 'City',
postal_code: 'PostCode'
};
if(allBindings.hasOwnProperty('AutocompleteOptions')) {
options = allBindings;
} else {
options = {
bounds: nzBounds,
componentRestrictions: { country: "nz" },
types: ['geocode']
};
}
autocomplete = new google.maps.places.Autocomplete(element,options);
autocomplete.addListener('place_changed',FillInAddress);
element.addEventListener('focus',Geolocate,false);
function FillInAddress() {
var place = autocomplete.getPlace();
context.$data.Latitude(place.geometry.location.lat());
context.$data.Longitude(place.geometry.location.lng());
for(var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if(componentForm[addressType]) {
var value = place.address_components[i][componentForm[addressType]];
if(addressForm.hasOwnProperty(addressType)) {
var key = addressForm[addressType];
context.$data[key](value);
}
}
}
}
function Geolocate() {
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy
});
autocomplete.setBounds(circle.getBounds());
});
}
}
}
};
})(ourProject);
//TODO I will separate the viewModel from the main js page that makes calls to the server when we get back to the project on January.Will also move the functions around so they are easier to follow and understand. This thing is getting big fast :)
$(function () {
var Property = (function (property) {
//Fields and model declarations
var self = property;
var token = $("input[name = '__RequestVerificationToken']").val();
var data = $.parseJSON($("#propModel").val());
var editUrl = $("#editUrl").val();
var deleteUrl = $("#deleteUrl").val();
var createUrl = $("#createUrl").val();
var updatePhotoUrl = $("#updatePhoto").val();
var forCreation = {};
console.log(data);
//Events, behaviors and observables
self.selectedProperty = ko.observable();
self.deletedProperty = ko.observable();
self.atIndex = ko.observable(true);
self.propertyTemplate = ko.observable("propertyIndex");
self.pickedTemplate = ko.observable();
self.properties = ko.observableArray();
self.property = ko.observable();
data.forEach(function (item) {
self.properties.push(new propertyViewModel(item));
});
//TODO I think this model should be picked up from the server when the page loads
(function () {
forCreation = {
Id: 0,
IsActive: true,
Address: {
Number: "",
Street: "",
AddressId: 0,
CountryId: 1,
City: "",
Latitude: "",
Longitude: "",
PostCode: "",
Suburb: ""
},
PhotoFiles: [],
Bathroom: 0,
Bedroom: 0,
CommercialType: "",
Description: "",
FloorArea: 0,
LandArea: 0,
Name: "",
ParkingSpace: 0,
PropertyType: "",
PropertyTypeId: 0,
ResidentialType: "",
TargetRent: 0,
TargetRentType: "",
YearBuilt: 0
};
self.property(new propertyViewModel(forCreation));
})();
function getPage() {
var anchor = $(this);
$.ajax({
url: anchor.attr("href"),
headers: {
"__RequestVerificationToken": token
},
type: "get"
}).done(function (result) {
console.log(result);
var target = anchor.parents("div.pagedList");
target.replaceWith(result);
self.properties.removeAll();
var parsed = $.parseJSON($("#propModel").val());
parsed.forEach(function (item) {
self.properties.push(new propertyViewModel(item));
});
});
return false;
}
$(".body-content").on("click", ".pagedList a", getPage);
self.goToIndex = ko.computed({
read: function () {
return self.propertyTemplate();
},
write: function () {
self.atIndex(true);
self.property(null);
self.property(new propertyViewModel(forCreation));
return self.propertyTemplate("propertyIndex");
}
});
self.viewDetails = ko.computed({
read: function () {
return self.propertyTemplate();
},
write: function (data) {
console.log(data);
self.selectedProperty(data);
self.atIndex(false);
return self.propertyTemplate("propertyDetails");
}
});
self.editProperty = ko.computed({
read: function () {
return self.propertyTemplate();
},
write: function (data) {
console.log(data);
self.selectedProperty(data);
self.atIndex(false);
return self.propertyTemplate("propertyForm");
}
});
self.showPropertyModal = function (data) {
self.pickedTemplate("deleteProperty");
self.deletedProperty(data);
};
self.deleteProperty = function (data) {
console.log(data);
$.ajax({
url: deleteUrl,
type: 'post',
headers: {
"__RequestVerificationToken": token
},
data: { id: data.Id() }
}).done(function (result) {
console.log(result);
if (result.success) {
self.properties.remove(function (property) {
return property.viewModel.Id() === result.id;
});
}
});
};
ko.bindingHandlers.saveProperty = {
init: function (elem, value, allProp, model, context) {
var accessor = ko.unwrap(value());
$(elem).on("click", function () {
var forSaving = ko.toJSON(accessor);
if (accessor.Id() === 0) {
$.ajax({
type: 'post',
url: createUrl,
headers: {
"__RequestVerificationToken": token
},
data: forSaving,
dataType: 'json',
contentType: 'application/json;charset=utf-8',
success: function (result) {
console.log(result);
console.log(context);
context.$data.Id(result.data.Id);
$("#propertyDetails").collapse('hide');
$("#photoUpload").collapse('show');
context.$parent.properties.push(new propertyViewModel(result));
self.property(null);
self.property(new propertyViewModel(forCreation));
}
});
}
else {
$.ajax({
type: 'post',
url: editUrl,
headers: {
"__RequestVerificationToken": token
},
data: forSaving,
dataType: 'json',
contentType: 'application/json;charset=utf-8',
success: function (result) {
console.log(result);
$("#propertyDetails").collapse('hide');
$("#photoUpload").collapse('show');
ko.mapping.fromJS(result, {}, context.$data);
}
});
}
return false;
});
}
};
ko.bindingHandlers.submitPhoto = {
init: function (elem, value, allProp, model, context) {
var accessor = ko.unwrap(value());
$(elem).on("click", function (event) {
var formData = new FormData();
var addedPhotos = accessor.PhotoFiles();
for (var i = 0; i < addedPhotos.length; i++) {
if (addedPhotos[i].Status === "add") {
formData.append("FileUpload" + i, addedPhotos[i].File);
}
}
for (var j = 0; j < accessor.currentPhotos.length; j++) {
if (accessor.currentPhotos[j].Status === "delete") {
formData.append(j, accessor.currentPhotos[j].Id);
}
}
console.log(formData);
$.ajax({
type: 'post',
url: updatePhotoUrl + "/" + accessor.Id(),
headers: {
"__RequestVerificationToken": token
},
data: formData,
dataType: 'json',
contentType: false,
processData: false,
success: function () {
$("#photoUpload").collapse('hide');
context.$parent.propertyTemplate("propertyIndex");
context.$parent.atIndex(true);
}
});
return false;
});
}
};
//TODO Put this on a separate file...I think I should put the data and its validations on a separate file as well..the viewModel will only hold behavior?
function propertyViewModel(item) {
function photoViewModel(photo) {
this.Id = photo.Id;
this.File = photo.File;
this.Data = photo.Data;
this.Status = photo.Status;
}
var self = this;
var mapOptions = {
"ignore": ["CreatedBy", "UpdatedBy", "UpdatedOn"],
"PhotoFiles": {
create: function (options) {
console.log(options);
if (options != null) {
return new photoViewModel(options.data);
}
}
}
};
ko.mapping.defaultOptions().include = [];
ko.validation.init({
grouping: {
deep: true,
observable: true,
live: true
}
});
self.viewModel = ko.mapping.fromJS(item, mapOptions);
self.validationModel = ko.validatedObservable(self.viewModel);
var address = self.viewModel.Address;
self.viewModel.currentPhotos = item.PhotoFiles;
self.viewModel.disableItem = ko.observable(true);
self.viewModel.Name.extend({
required: {
params: true,
message: "Please include your property's name."
},
pattern: {
params: "^[A-Za-z0-9 ]{4,30}$",
message: "The Name field must be alphanumeric and must be between 4-30 characters."
}
});
self.viewModel.Bathroom.extend({
number: {
params: true,
onlyIf: isPropResidential,
message: "The Bathroom field must be a number."
},
pattern: {
params: "^[0-9]{0,2}$",
message: "The Bathroom field must be a number and musn't exceed two digits."
}
});
self.viewModel.Bedroom.extend({
number: {
params: true,
onlyIf: isPropResidential,
message: "The Bedroom field must be a number."
},
pattern: {
params: "^[0-9]{0,2}$",
message: "The Bedroom field must be a number and musn't exceed two digits."
}
});
self.viewModel.ParkingSpace.extend({
number: {
params: true,
message: "The Parking Space field must be a number."
},
pattern: {
params: "^[0-9]{0,3}$",
message: "The Parking Space field must be a number and musn't exceed three digits."
}
});
self.viewModel.FloorArea.extend({
number: {
params: true,
onlyIf: isPropResidential,
message: "The Floor Area field must be a number."
},
pattern: {
params: "^[0-9]{0,6}$",
message: "The Floor Area field must be a number and musn't exceed six digits."
}
});
self.viewModel.LandArea.extend({
number: {
params: true,
onlyIf: isPropResidential,
message: "The Land Area field must be a number."
},
pattern: {
params: "^[0-9]{0,6}$",
message: "The Land Area field must be a number and musn't exceed six digits."
}
});
self.viewModel.YearBuilt.extend({
number: {
params: true,
message: "The Year Built field must be a number."
},
pattern: {
params: "^[0-9]{4}$",
message: "The Year Built field must be a number and exactly four digits."
}
});
self.viewModel.TargetRent.extend({
number: {
params: true,
message: "The Target Rent field must be a number."
},
pattern: {
params: "^[0-9]{0,6}$",
message: "The Target Rent field must be a number and musn't exceed six digits."
}
});
self.viewModel.Address.Number.extend({
required: {
params: true,
message: "Please include your Number."
},
pattern: {
params: "[A-Za-z0-9 ]",
message: "The Number field must be alphanumeric characters."
}
});
self.viewModel.Address.Street.extend({
required: {
params: true,
message: "Please include your Street."
},
pattern: {
params: "[A-Za-z0-9 ]",
message: "The Street field must be alphanumeric characters."
}
});
self.viewModel.Address.PostCode.extend({
required: {
params: true,
message: "Please include your PostCode."
},
pattern: {
params: "[A-Za-z0-9 ]",
message: "The Post Code field must be alphanumeric characters."
}
});
self.viewModel.Address.City.extend({
required: {
params: true,
message: "Please include your City."
},
pattern: {
params: "[A-Za-z0-9 ]",
message: "The City field must be alphanumeric characters."
}
});
self.viewModel.Address.Suburb.extend({
required: {
params: true,
message: "Please include your Suburb."
},
pattern: {
params: "[A-Za-z0-9 ]",
message: "The Suburb field must be alphanumeric characters."
}
});
function isPropResidential() {
return self.viewModel.PropertyType() === "Residential" ? true : false;
}
self.viewModel.fullAddress = ko.computed(function () {
var val = address.Number() + ", " + address.Street() + ", "
+ address.Suburb() + ", " + address.City() + ", "
+ address.PostCode();
return val;
});
self.viewModel.rooms = ko.computed(function () {
var val = "Bedrooms: " + self.viewModel.Bedroom() + ", "
+ "Bathrooms: " + self.viewModel.Bathroom();
return val;
});
self.viewModel.togglePropType = ko.pureComputed(function () {
return isPropResidential();
});
self.viewModel.toggleDisable = ko.computed({
read: function () {
if (!self.validationModel.isValid()) {
self.validationModel.errors.showAllMessages();
self.viewModel.disableItem(true);
}
else {
self.viewModel.disableItem(false);
}
return self.viewModel.disableItem();
}
});
self.viewModel.propertyTypeOption = ["Residential", "Commercial"];
self.viewModel.validFileTypes = [
"image/jpeg",
"image/png",
"image/gif"
];
self.viewModel.propertyResidentialSubTypeOption = [
{
key: "Affordable Housing",
value: 1
},
{
key: "Section and Property",
value: 2
},
{
key: "Senior Housing",
value: 3
},
{
key: "Short-Term Rental",
value: 4
},
{
key: "Student Housing",
value: 5
},
{
key: "Vacation Property",
value: 6
}];
self.viewModel.propertyCommercialSubTypeOption = [
{
key: "Car Park",
value: 7
},
{
key: "Development Site",
value: 8
},
{
key: "Hotel/Leisure",
value: 9
},
{
key: "Industrial",
value: 10
},
{
key: "Office",
value: 11
},
{
key: "Retail",
value: 12
}];
self.viewModel.fileUpload = function (data, event) {
var files = event.target.files;
console.log(data);
self.validationModel.isValid(true);
for (var i = 0; i < files.length; i++) {
if (!~self.viewModel.validFileTypes.indexOf(files[i].type)) {
console.log(self.viewModel.PhotoFiles().length);
if (self.viewModel.PhotoFiles().length <= 0) {
self.validationModel.isValid(false);
}
alert("Supported file types are *.jpg, *.jpeg, *.png, *.gif");
break;
}
else {
(function (file) {
var reader = new FileReader();
reader.onloadend = function (e) {
var result = e.target.result;
var photo = {
Data: result,
File: file,
Status: "add"
};
self.viewModel.PhotoFiles.mappedCreate(photo);
};
reader.readAsDataURL(file);
})(files[i]);
}
}
$("#editPropertyForm")[0].reset();
};
self.viewModel.removePhoto = function (photo) {
if (photo.Status === "load") {
var idx = self.viewModel.currentPhotos.findIndex(x => x.Id === photo.Id);
self.viewModel.currentPhotos[idx].Status = "delete";
}
self.viewModel.PhotoFiles.mappedRemove(photo);
};
}
console.log(self);
ko.applyBindings(property);
})(Property = Property || (Property = {}));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment