Monthly Archives: April 2015

Gitignore template and Git Remove .DS_Store from commits.

.gitignore

/.buildpath
/build/
*/archive/

__MACOSX
.DS_Store

.project
.settings
.classpath
.sass-cache/

# OS generated files #
######################
*/.DS_Store
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db

# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip

# Logs and databases #
######################
*.log
*.sql
*.sqlite

# Modules, components, dist, tmp #
##################################
node_modules
bower_components
dist
.tmp
.sass-cache

This is nice to remember.

$ git rm --cached .DS_Store

AngularJS – State Service

‘use strict’;

/**
* @ngdoc service
* @name interceptcms.stateService
* @description
* # stateService
* Factory in the interceptcms.
*/

function stateHistory($state) {

var history = [];
var idx = 0;

// function loadPrevious(idx) {
//   $state.go(history[idx].state, history[idx].params);     
// }

function addState() {
  history.push({state: $state.current, params: $state.params});
  idx += 1;
}

return {

    loadListView: function(model) {
        for (var i = history.length - 1; i >= 0; i--) {
          if (history[i].params.id === undefined && history[i].params.model === model) {
            $state.go(history[i].state, history[i].params); 
          }
        }   
    },

    loadRecordView: function(model, id) {
        var params = {};
        params.id = id.replace('/', '');
        params.model = model;
        $state.go("/:model/:id", params); 
        //$state.transitionTo("/:model/:id", params, { reload: true, inherit: true, notify: true });
    },

      // FORM SUBMISSION CREATES AN ADDITIONAL STATE
    goBack: function (model, createEdit, id) {
        //loadListView($state.params.model);      
        if (id) {
          this.loadRecordView(model, id);
        } 
    },

    stateLoaded: function () {
        addState();
    },

    listView: function(model, id) {
        if (!id) {
          this.loadListView(model);
        } else {
          this.loadRecordView(model, id);
        }
    }

};

}

angular.module(‘interceptcms’)
.factory(‘stateService’, stateHistory);

AngularJS – State Model Data Service

‘use strict’;

/**
* @ngdoc service
* @name interceptcms.config
* @description
* # config
* Factory in the interceptcms.
*/

var stateModelDataService = function ($state, dataService, modelService, RESOURCES) {

var setConfig = function(query, params) {
    var config = {};
    config.searchField = query.field || 'tag';
    config.criteria = decodeURIComponent(params.id) || "";
    config.skip = query.skip || 0;
    config.limit = query.limit || RESOURCES.LIMIT;
    config.dir = query.dir || 'asc';
    config.sort = {};
    config.query = {};
    if (config.query.sort) {
        config.sort[config.query.sort] = config.dir;
    } else {
        config.sort[primaryField] = config.dir;
    }
    return config;
};

var isUpdate = function (array) {
    return (array.indexOf(RESOURCES.EDIT) > -1) ? true : false;
};

var isCreate = function (array) {
    return (array.indexOf(RESOURCES.CREATE) > -1) ? true : false;
};

return function (callback, dataFiltered, setScopeValue) {

    var urlArray = [];
    // CONTROLLER VARIABLES
    // DYNAMIC URL       
    if ($state.params.model) {
        urlArray.push($state.params.model);
        if ($state.params.view) {
            urlArray.push($state.params.view);
        }
        if ($state.params.id) {
            urlArray.push($state.params.id);
        }
    } else {
        // EXPLICIT URL
        urlArray = $state.current.name.split('/'); 
    }

    // SET UP CONFIG
    // DEFINE MODEL / ISCREATE / ISUPDATE / ID / BASEURL
    var qs = window.location.href.split('?')[1];
    var config = {};
        config.model = {};
        config.model._modelName = urlArray[0];
        config.isCreate = isCreate(urlArray);
        config.isUpdate = isUpdate(urlArray);
        config.id = ($state.params.id) ? "/" + $state.params.id : "";
        config.baseUrl = RESOURCES.API_URL + urlArray[0];

        config.search = ($state.params.search) ? "/search/" + $state.params.search : "";
        config.qs = (qs) ? "?" + qs : "";
        if (config.search === "") {
            config.qs = config.qs.replace("&dir=", " ");
        }

    // REQUEST AND RETURN MODEL DEFINITION
    // PASSED TO CONTROLLER FUNCTION setScopeValue
    if (config.model._modelName !== "") {
        modelService.requestModel(config.model._modelName, setScopeValue);
    }

    // RETURN CONFIG TO CONTROLLER / CALLBACK
    callback(config);

    // RETURN DATA TO DATAFILTERED FOR OPTIONAL LOCAL FILTERING
    if (!config.isCreate && config.model._modelName !== "") {
        dataService.getData(config.baseUrl + config.id + config.search + config.qs, config.model, dataFiltered);
    }
};

};

angular.module(‘interceptcms’)
.factory(‘stateModelDataService’, stateModelDataService);

AngularJS – Data Service

‘use strict’;

/**
* @ngdoc service
* @name interceptcms.dataService
* @description
* # dataService
* Service in the interceptcms.
*/

angular.module(‘interceptcms’)
.factory(‘dataService’, function($http, $state, $log, alert, stateService, authToken, utils, socketService, sailsValidation, RESOURCES) {

    // USE SOCKET IO TO SEND OUT NOTIFICATION OF RECORD UPDATE
    function sendNotification (user, room, msg, link) {
        socketService.emit('message', user, { room: room, msg: msg + ": " + link});
    }

    // CREATE NOTIFICATION RECORD IN THE DB
    // SEND NOTIFICATION TO USER
    function postNotificationToDB(APP_URL, model, room, action, title, id, updatedValues, user) {
        var notification = {
            model: model,
            action: action,
            title: title,
            updatedValues: updatedValues.join(','),
            link: "<a href='" + APP_URL + "#/" + model + id + "'>Link</a>",
            user: user
        };

        // TO DO GET LIST OF USERS WATCHING ACTIONS IN DB
        // SEND AS AN ARRAY IN THE QUERY STRING TO POST NOTIFICATIONS TO DB
        var user = authToken.getUsername();

        // SEND NOTIFICATION TO USERS
        //$http.post(RESOURCES.API_URL + 'notification/?user=' + user, notification).success(function(data) {
        $http.post(RESOURCES.API_URL + 'notification/', notification).success(function(data) {
            var notificationRoom = room + ": " + model;
            var notificationMsg = model + " " + action + " '" + title + "' ";
            getUsersToNotify("admin", notificationRoom, notificationMsg, notification.link, data.id);
        });
    }

    function getUsersToNotify(type, room, msg, link, id) {
        $http.get(RESOURCES.API_URL + 'user/?userType=' + type).success(function(data) {
            for (var i = data.length - 1; i >= 0; i--) {
                var tmpObj = {notification: id, user: data[i].name};
                $http.post(RESOURCES.API_URL + 'usernotification/', tmpObj);
            }
            for (var i = data.length - 1; i >= 0; i--) {
                sendNotification(data[i].name, room, msg, link);
            };
        });
    }
    // RETURNED OBJECT
    var dataService = {};
    // SET NOTIFICATION TYPE
    var room = 'notifications';

    // GET DATA FROM API
    dataService.getData = function (url, model, callback) {
        $http.get(url).success(function(obj) {
            // ADD NEW STATE TO HISTORY
            stateService.stateLoaded();
            return callback(obj, model);
        }).error(function(err){
            alert('warning', "Unable to get " + model + ".", err.message);
        });
    };

    dataService.confirmDelete = function(model, id, user) {
       var answer = window.prompt("Are you sure you want to delete this record? \r Type 'y' to delete.");

        if (answer === 'y') {
            dataService.deleteRecord(model, id, user);
        }
    };

    dataService.deleteRecord = function(model, id, user) {
        // SET MSG VARIABLES
        var title = "";
        var updatedValues = [];

        // RECORD DB NOTIFICATION TO USER
        // SEND NOTIFICATION TO USER
        postNotificationToDB(RESOURCES.APP_URL, model, room, "Deleted", title, id, updatedValues, user);
        $http.get(RESOURCES.API_URL + model + "/destroy/" + id);
        stateService.listView(model);
    };
    // EXTERNAL MODELS ARE QUERIED FOR MODEL PRIMARY FIELD AND ID 
    // THIS DATA IS USED WHEN CREATING DROPDOWNS IN THE UI
    dataService.getRelatedData = function(val, model, callback) {
        $http.get(RESOURCES.API_URL + model + "/get").success(function(obj) {
            val = "dd_" + val;
            return callback(val, obj);
        }).error(function(err){
            alert('warning', "Unable to get " + model.trim() + ".", err.message);
        });
    };

    // STRIP OUT UNEDITED PROPERTIES BEFORE SUBMITTING 
    // ALLOWS US TO TRACK MODIFICATIONS
    // IF THE OBJECT IS MODIFIED SUBMIT / ELSE SEND ALERT TO USER
    dataService.processBeforeSubmitting = function(originalObj, editedObj) {
        var obj = utils.compareObjectsKeepChanges(originalObj, editedObj);
        if (obj) {
            submit(obj);
        } else {
            alert('warning', "No changes were found.");             
        }
    };

    //  SUBMIT DATA TO API
    function submit(obj) {
        var user = authToken.getUsername();
        // SET UP 
        var model = ($state.params.model) ? $state.params.model : $state.current.name.split('/')[0];
        var url = RESOURCES.API_URL + model;
        var id = ($state.params.id) ? '/' + $state.params.id : "";
        var submittedMsg = (id) ? "Updated" : "Created";
        var createEdit  = true;

        // REMOVE RECORD KEEPING ELEMENTS FROM OBJECT
        obj = utils.stripObj(obj, RESOURCES.RECORD_KEEPING_DATA);

        // SET NOTIFICATION TO SPECIFY UPDATED PROPERTIES
        var updatedValues = [];
        if (id) {
            for (var k in obj) {
                updatedValues.push(k);
            }
        }

        // UPDATE EXISTING OBJECT
        // TO INCLUDE UPDATED OR CREATED BY
        if (id) {
            url += id;
            obj.updatedBy = user;
        } else {
            obj.createdBy = user;
        }

        // POST DATA TO API
        $http.post(RESOURCES.API_URL + model + id, obj).success(function(data) {
            // SET MSG VARIABLES
            var title = (obj[model] !== undefined) ? obj[model] : obj.title;

            // IF RECORD WAS CREATED
            // GET AND SET NEW RECORDS ID
            if (!id) {
                id = "/" + data.id;
            }
            // SEND ALERT TO USER
            alert("success", model + submittedMsg, "Thank you.");
            // RECORD DB NOTIFICATION TO USER
            // SEND NOTIFICATION TO USER
            postNotificationToDB(RESOURCES.APP_URL, model, room, submittedMsg, title, id, updatedValues, user);
            stateService.loadRecordView(model, id);
        }).error(function (data) {
            var err = {};
            err.model = model;
            if (data.raw) {
                if (data.raw.code === 11000) {
                    err.msg = data.raw.err;
                }
            }
            sailsValidation(err);
        });
    }

    return dataService;

});

AngularJS – Model Service

‘use strict’;

/**
* @ngdoc service
* @name interceptcms.modelService
* @description
* # modelService
* Factory in the interceptcms.
*/
angular.module(‘interceptcms’)
.factory(‘modelService’, function($http, alert, RESOURCES) {

// LOAD MODELS ONCE AND SAVE TO cachedModels
var cachedModels = {};

//  GET MODEL FROM API
//  RETURNS OBJECT STRUCTURE AND DATA TYPES
//  ADDS MODEL NAME PROPERTY
//  PASSES TO AUGMENTMODEL
function getModel(modelName, callback) {
  $http.get(RESOURCES.API_URL + modelName + "/model").success(function(obj) {
    var model = {};
    model = augmentModel(obj);
    model._modelName = modelName;
    if (callback) {
      callback("model", model);
    } else {
      return model;
    }
    setModel(modelName, model);
  }).error(function(err){
    alert('warning', "Unable to get " + modelName + ".", err.message);
  });
}

function setModel(modelName, model) {
  cachedModels[modelName] = model;
}

//  AUGMENTMODEL
//  IDENTIFIES RECORD KEEPING DATA AS ARRAY
//  IDENTIFIES RELATED MODELS WITHIN MODEL
//  IDENTIFIES DROPDOWN MODELS WITHIN MODEL
//    ADDS RESULT TO PROPERTY relatedModels

// UPDATE MODEL WITH GENERIC PROPERTIES
// CALLED BY GET MODEL
// ADDS VIEW SPECIFIC INFO
// TO GENERATE VIEW COMPONENTS FROM RELATED DATA SETS
function augmentModel(obj){
  var relatedArray = [];
  var dropdownArray = [];
  for (var prop in obj) {

    // TO DO REFACTOR RELATIONSHIP DESCRIPTION 
    // COLLECTION = ONE TO MANY
    if (obj[prop].collection) {
      // MONGO SPECIFIC
      relatedArray.push(checkRelated(obj, prop, "collection", "collection"));
    }

    // MODEL = ONE TO ONE using VIA AS field REFERENCE
    if (obj[prop].model) {
      // REFACTOR 
      // USE GETDROPDOWNVALUES TO PROPULATE AS OBJECT
      // WITH NAME PROPERTY & VALUE ARRAY
      // MONGO SPECIFIC
      dropdownArray.push(checkRelated(obj, prop, "model"));
    }
  }

  obj._rkData = RESOURCES.RECORD_KEEPING_DATA;
  obj._relatedModels = relatedArray;
  obj._dropdownArray = dropdownArray;
  return obj;
}

// CREATE RELATEDARRAY ELEMENTS
// CREATE dropdownArray ELEMENTS
// FOR VIEW
function checkRelated(obj, prop, type) {
  //if (obj[prop][type]) {
    var tmpObj = {};
    tmpObj[prop] = obj[prop][type];
    return tmpObj;
  //}
}

var requestModel = function(modelName, callback) {
  if (!cachedModels[modelName]) {
    getModel(modelName, callback);
  } else {
    console.log("returning cached model:" + modelName);
    return callback("model", cachedModels[modelName]);
  }
};
var getCachedModel = function(modelName) {
  var model = cachedModels[modelName];
  var array = [];
  for (var prop in model) {
    array.push(prop);
  }
  return array;
};
var getDropDownModel = function(modelName) {

};

return {
  requestModel: requestModel,
  getCachedModel: getCachedModel,
  getModel: getModel
};

});

AngularJS – Utilities

‘use strict’;

/**
* @ngdoc service
* @name interceptcms.utilities
* @description
* # utilities
* Service in the interceptcms.
*/

var initInjector = angular.injector([‘ng’]);
var $http = initInjector.get(‘$http’);

angular.module(‘interceptcms’).service(‘utils’, function () {

var utilities = {

    // PRESUBMIT OBJECT MODIFICATION
    // REMOVE UNCHANGED PROPERTIES BEFORE UPDATE
    // ONLY SUBMIT CHANGED PROPS

    // OBJECTS PROPERTIES ARE COMPARED TO THE ORIGINAL OBJECT
    // ONLY CHANGED VALUES ARE SUBMITTED TO THE DB
    // VALUES THAT MATCH THE ORIGINAL OBJECT ARE STRIPPED BEFORE SUBMITTING

    // ALLOWS US TO TRACK THE CHANGED PROPS IN THE SUBMITTED OBJ
    compareObjectsKeepChanges: function (originalObj, editedObj){
        for(var prop in editedObj) {
            if (typeof originalObj[prop] === "object") {
                if (this.jsonObj(originalObj[prop]) === this.jsonObj(editedObj[prop])) {
                    delete editedObj[prop];
                }
            } else {
                if (editedObj[prop] === originalObj[prop]) {
                    delete editedObj[prop];
                }
            }
        }
        if (!this.isObject(editedObj)) {
            return;
        } else {
            return editedObj;
        }
    },

    // VERIFY THAT THERE IS OBJECT
    isObject: function (newObj) {
        return (Object.keys(newObj).length > 0);
    },

    // STRINGIFY OBJECT ARRAYS OR OBJECTS WITHIN AN OBJECT
    jsonObj: function (obj) {
        return JSON.stringify(obj);
    },

    // REMOVE PROPERTIES FROM OBJECT
    stripObj: function (obj, propsToRemove) {
        if (obj) {
            for (var i = propsToRemove.length; i >= 0; i--) {
                if (obj[propsToRemove[i]]) {
                    delete obj[propsToRemove[i]];
                }
            }
        }
        return obj;
    },

    getUrl: function(url) {
        $http.get(url).success(function(obj) {
            return obj;
        }).error(function(err){
            return err;
        });
    }
};

return utilities;

});

AngularJS Configuration File – app.config.js

‘use strict’;

angular.module(‘interceptcms’)

// USED TEMPORARILY FOR TRACKING STATE CHANGE
// STATE CHANGE SUCCESS ISN’T BEING CALLED
.run(function($rootScope) {
$rootScope.$on(“$stateChangeSuccess”, function(event, toState, toParams, fromState, fromParams) {
if (fromState.name === “”) {
// The initial transition comes from “root”, which uses the empty string as a name.
//console.log(“initial state: ” + toState.name);
} else {
console.log(“NEW state: ” + toState.name);
}
});
})

// NOTE ———————————————–
// THE CREATE STATE REUSES THE EDIT VIEW FOR ALL MODELS
// IF TEMPLATES NEED TO BE SEPARATE CHANGE
// the create objects templateUrl property
// in app.routes.js

.constant(‘RESOURCES’, (function() {
// Define your variable
var HOSTNAME = window.location.hostname;
// Use the variable in your constants
return {

// HOSTNAME
HOSTNAME: HOSTNAME,

// APPLICATON NAME USED IN HEADER
APP_NAME: "Online Publications",

// URLS

    // FRONTEND APP
    APP_URL: "http://" + HOSTNAME + ":9000/",

    // AUTHORIZATION APP
    AUTH_URL: "http://" + HOSTNAME + ":3000/",

    // DATA FEED
    API_URL: "http://" + HOSTNAME + ":1337/",

    // NOTIFICATIONS SERVER
    SOCKETIO_SERVER: "http://" + HOSTNAME + ":3000/",

// DIRECTORIES

    // DEFAULT VIEWS FOLDER
    VIEWS: '/views/',

    // USED IF FEATURE NESTING IS TRUE
    COMPONENTS: "/components/",

    // CONTROLLERS
    CONTROLLERS: "../controllers/",


// VIEWS EXT
VIEW_EXT: '.html',


// USED IF FEATURE NESTING IS FALSE / AND URLS ARE AUTO GENEREATED
DEFAULTS: "defaults",


// STATE DEFINITIONS / URL PARTIALS / ACTIONS

    // CREATE URL AND FILE REFERENCE FOR EACH MODEL
    CREATE: "create",

    // LIST URL AND FILE REFERENCE FOR EACH MODEL
    LIST: "list",

    // VIEW URL AND FILE REFERENCE FOR EACH MODEL
    VIEW: "view",

    // EDIT URL AND FILE REFERENCE FOR EACH MODEL
    EDIT: "edit",

    // SEARCH STRING DELIMITER
    SEARCH: "search",


// A LIST OF FIELDS FOR ALL MODELS THAT ARE FOR INTERNAL / CMS USE ONLY
RECORD_KEEPING_DATA: ['id', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy'],

// A LIST OF FIELDS FOR ALL MODELS THAT ARE FOR INTERNAL / CMS USE ONLY
CTRLS: ['DefaultCtrl', 'TagtypesCtrl', 'TagCtrl'],

// DEFAULT_IMG REFERENCE FOR EACH MODEL
DEFAULT_IMG: "http://placehold.it/400x300",

// ARRAY of Controllers with additional / static / custom routing needs
// CREATE AN ARRAY OF OBJECTS WITH STATE, CTRL and FEATURENESTING PROPERTIES
// ex: [{ state:'essay', ctrl:"EssaysCtrl", featureNesting: false }]
STATECTRLS: [],

LIMIT: 20,

DIR: "desc"

};
})());
/*
// AUTO GENERATED ROUTING
// **** REQUIREMENTS ****

// STATE MUST MATCH
// API MODEL/CLASS NAME
// ex. car

// VIEW MUST BE NAMED
// state-constant VIEW_EXT
// ex. car-edit.html

// MODEL DEFINITION
// MUST BE AVAILABLE FROM
// API_URL/state/model
// ex. localhost:33777/car/model

.constant(‘STATECTRLS’, [
// { state:’essay’, ctrl:”EssaysCtrl”, featureNesting: false },
// { state:’tag’, ctrl:”TagsCtrl”, featureNesting: false },
// { state:’tagType’, ctrl:”TagtypesCtrl”, featureNesting: false }
]
);

*/

Angular JS – UI-Router – Dynamic Routing

‘use strict’;

var templateCache = {};
var controllerCache = {};

angular.module(‘interceptcms’).config(function($urlRouterProvider, $stateProvider, $httpProvider, RESOURCES) {

//$httpProvider.defaults.cache = true;

var url404 = "404";

// PASCAL CASE IS THE PREFERRED ANGULAR NAMING CONVENTION 
// FOR CONTROLLERS
function capitalizeFirstletter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

function getUrl(url) {
    var http = new XMLHttpRequest();
    http.open("GET", url, false);
    http.send();
    if (http.status === 404) {
        return;
    }
    return http.response;
}

// EVERYTHING IS GOING TO a single controller
// ANGULAR DIRECTIVES are being called dynamically via the view
// defaultCtrl.js performs generic data to api actions
function checkControllerCache() {
    var model = window.location.hash.split('/')[1] || 'default';
    if (model.indexOf('?') > -1) {
        model = model.split('?')[0];
    }
    var modelRef = capitalizeFirstletter(model) + "Ctrl";

    var ctrl = getControllerCache(model);

    if (ctrl) {
        //console.log("returning cached Controller " + ctrl);
    } else {
        var partial = model + "/" + model + ".js";
        if (getUrl(RESOURCES.COMPONENTS + partial) || getUrl(RESOURCES.CONTROLLERS + partial)) {
            setControllerCache(model, modelRef);
        } else {
            setControllerCache(model, 'DefaultCtrl');
            ctrl = 'DefaultCtrl';
        }
    }
    return ctrl;
}

function setControllerCache(ctlrRef, ctrl) {
    controllerCache[ctlrRef] = ctrl;
}

function getControllerCache(ctlrRef) {
    return controllerCache[ctlrRef];
}

// TEMPLATE CACHE
function setTemplateCache(templateName, view) {
    templateCache[templateName] = view;
}

function getTemplateCache(templateName) {
    return templateCache[templateName];
}

function checkTemplateCache(model, template) {

    var tmpl = getTemplateCache(model + template);
    var url;

    // RETURNS CACHED TEMPLATE
    if (tmpl) {
        console.log("returning cached view " + model + ":" + template);
    } else {
        url = RESOURCES.COMPONENTS + model + "/" + template + RESOURCES.VIEW_EXT;
        // RETURNS CUSTOM TEMPLATE
        tmpl = getUrl(url);

        // RETURNS DEFAULT TEMPLATE
        if (!tmpl) {
            url = RESOURCES.VIEWS + RESOURCES.DEFAULTS + "/" + template + RESOURCES.VIEW_EXT;
            tmpl = getUrl(url);
        }

        // RETURNS 404
        if (!tmpl) {
            tmpl = getUrl(RESOURCES.VIEWS + url404 + RESOURCES.VIEW_EXT);
        }
        setTemplateCache(model+template, tmpl);
    }
    return tmpl;
}

// DEFAULT ROUTE
$urlRouterProvider.otherwise('/');

// EXPLICITLY SET ROUTES
// GENERIC APP FUNCTIONALITY

$stateProvider
.state('main', {
    url: '/',
    templateUrl: RESOURCES.VIEWS + 'main' + RESOURCES.VIEW_EXT
})

.state('register', {
    url: '/register',
    templateUrl: RESOURCES.VIEWS + 'register' + RESOURCES.VIEW_EXT,
    controller: 'RegisterCtrl'

})

.state('login', {
    url: '/login',
    templateUrl: RESOURCES.VIEWS + 'login' + RESOURCES.VIEW_EXT,
    controller: 'LoginCtrl'
})

.state('forgot', {
    url: '/forgot',
    templateUrl: RESOURCES.VIEWS + 'forgot' + RESOURCES.VIEW_EXT,
    controller: 'ForgotCtrl'
})

.state('reset/:id', {
    url: '/reset/:id',
    templateUrl: RESOURCES.VIEWS + 'reset' + RESOURCES.VIEW_EXT,
    controller: 'ResetCtrl'
})


.state('logout', {
    url: '/logout',
    controller: 'LogoutCtrl'
});


// DYNAMICALLY ADD STATES FROM ARRAY 
// CREATED ON PAGE LOAD
// CREATE AN ARRAY OF OBJECTS WITH STATE, CTRL and FEATURENESTING PROPERTIES
// ex: [{ state:'essay', ctrl:"EssaysCtrl", featureNesting: false }]

function createState(stateCtrl){
    var ctrl = stateCtrl.ctrl;
    var state = stateCtrl.state;
    var path = (stateCtrl.featureNesting) ? RESOURCES.COMPONENTS + "/" + state + "/" : RESOURCES.VIEWS;
    var list = {
        url: "/" + state ,
        templateUrl: path + state + "-" + RESOURCES.LIST + RESOURCES.VIEW_EXT,
        controller: ctrl
    };

    var create = {
        url: "/" + state + "/" + RESOURCES.CREATE,
        templateUrl: path + state + "-" + RESOURCES.EDIT + RESOURCES.VIEW_EXT,
        controller: ctrl    
    };

    var view = {
        url: "/" + state + "/:id",
        templateUrl: path + state + "-" + RESOURCES.VIEW + RESOURCES.VIEW_EXT,
        controller: ctrl
    };

    var edit = {
        url: "/" + state + "/:id" + RESOURCES.EDIT,
        templateUrl: path + state + "-" + RESOURCES.EDIT + RESOURCES.VIEW_EXT,
        controller: ctrl
    };

    $stateProvider.state(state, list);
    $stateProvider.state(state + "/" + RESOURCES.CREATE, create);
    $stateProvider.state(state + "/", view);
    $stateProvider.state(state + "/" + RESOURCES.EDIT + "/", edit);
}

// ADD NEW STATES IN FOR EACH LOOP
angular.forEach(RESOURCES.STATECTRLS, function (stateCtrl) {
    createState(stateCtrl);
});

// FULL DYNAMIC ROUTING
// CHECKS FOR CUSTOM CONTROLLER NESTED IN COMPONENTS
// CHECKS FOR CUSTOM CONTROLLER IN CONTROLLERS
// FAILS TO GENERIC DEFAULT CONTROLLER
// CHECKS FOR CUSTOM VIEWS NESTED IN COMPONENTS
// FAILS TO GENERIC VIEWS

$stateProvider

// EDIT RECORD
.state('/:model/:id/' + RESOURCES.EDIT, {
    url: '/:model/:id/' + RESOURCES.EDIT,
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.EDIT);
    },
    controller: checkControllerCache()
})

// CREATE RECORD
.state('/:model/' + RESOURCES.CREATE, {
    url: '/:model/' + RESOURCES.CREATE,
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.EDIT);
    },
    controller: checkControllerCache()
})

// LIST VIEW
.state('/:model/' + RESOURCES.LIST, {
    url: '/:model/' + RESOURCES.LIST,
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.LIST);
    },
    controller: checkControllerCache()
})

// RECORD VIEW
.state('/:model/:id', {
    url: '/:model/:id',
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.VIEW);   
    },
    controller: checkControllerCache()
})

// SEARCH LIST VIEW
.state('/:model/' + RESOURCES.SEARCH + "/:search?:skip&:limit", {
    url: '/:model/' + RESOURCES.SEARCH + "/:search?:skip&:limit",
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.LIST);
    },
    controller: checkControllerCache()
})

// SEARCH LIST VIEW
.state('/:model/' + RESOURCES.SEARCH + "/:search", {
    url: '/:model/' + RESOURCES.SEARCH + "/:search",
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.LIST);
    },
    controller: checkControllerCache()
})

// ADDITIONAL LIST VIEW
.state('/:model/' + RESOURCES.LIST + '/:page', {
    url: '/:model/' + RESOURCES.LIST + '/:page',
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, stateParams.page);
    },
    controller: checkControllerCache()
})

// ADDITIONAL ITEM VIEW
.state('/:model/:id/:page', {
    url: '/:model/:id/:page',
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, stateParams.page); 
    },
    controller: checkControllerCache()
})

// DEFAULT LIST VIEW
.state('/:model?skip:skip&limit:limit', {
    url: '/:model?:skip&:limit',
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.LIST);   
    },
    controller: checkControllerCache()
})

.state('/:model', {
    url: '/:model',
    template: function(stateParams) {
        return checkTemplateCache(stateParams.model, RESOURCES.LIST);   
    },
    controller: checkControllerCache()
});

$httpProvider.interceptors.push('authInterceptor');

});

Create Node based Chrome Extension

Creating a Node based Chrome Extension starter project is easy using yo’s chrome-chrome-extension generator.

To start, make sure you have yo and the generator installed.

$  npm install -g yo
$  npm install -g generator-chrome-extension

Then.

$  mkdir your-new-ext-name; cd your-new-ext-name
$  yo chrome-extension --skip-install

From chrome://extensions/

Press Load upacked extension…

Select your app.

Yup.

Still working on getting this to work with Ember-Cli.

Looks like .bowerrc, bower.json, and package.json need to be reworked.

.bowerrc

{
  "directory": "bower_components",
  "analytics": false
}

bower.json

{
  "name": "your-new-ext-name",
  "dependencies": {
    "ember": "1.11.1",
    "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3",
    "ember-cli-test-loader": "ember-cli-test-loader#0.1.3",
    "ember-data": "1.0.0-beta.16.1",
    "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.4",
    "ember-qunit": "0.3.1",
    "ember-qunit-notifications": "0.0.7",
    "ember-resolver": "~0.1.15",
    "jquery": "^1.11.1",
    "loader.js": "ember-cli/loader.js#3.2.0",
    "qunit": "~1.17.1"
  }
}

In package.json update the following

  "scripts": {
    "start": "ember server",
    "build": "ember build",
    "test": "ember test",
    "postinstall": "./node_modules/bower/bin/bower install"
  },
  "repository": "",
  "engines": {
    "node": ">= 0.10.0"
  },
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "bower": "^1.4.1",
    "broccoli-asset-rev": "^2.0.2",
    "ember-browserify": "^0.6.4",
    "ember-cli": "0.2.3",
    "ember-cli-app-version": "0.3.3",
    "ember-cli-babel": "^5.0.0",
    "ember-cli-content-security-policy": "^0.4.0",
    "ember-cli-dependency-checker": "0.0.8",
    "ember-cli-htmlbars": "0.7.4",
    "ember-cli-ic-ajax": "0.1.1",
    "ember-cli-inject-live-reload": "^1.3.0",
    "ember-cli-qunit": "0.3.10",
    "ember-cli-uglify": "1.0.1",
    "ember-data": "1.0.0-beta.16.1",
    "ember-export-application-global": "^1.0.2"
  }

Re-run

$ npm install

Ember.js – Image Component with backup Default Image

Because of things outside of our control as developers, images don’t always load and the design suffers. This occurs because of a connectivity issues, or when an image path is incorrect or points to a resource that is no longer available.

In the interest of graceful failure, I include a default image as a backup for images that don’t load.

components/default-img.js

import Ember from 'ember';

export default Ember.Component.extend({
    tagName: 'img',
    attributeBindings: ['src'],

    didInsertElement: function(){
        var _this = this;

        // With jQuery
        this.$().on('load', function(evt){
            return _this.imageLoaded(evt);
        }).on('error', function(evt){
            return _this.errorLoading(evt);
        });

        // Without jQuery / for Mobile
        /*
        var domRef = document.getElementById(this.get('elementId'));
        domRef.addEventListener('load', function (evt) {
            return _this.imageLoaded(evt);
        }, false);
        domRef.addEventListener("error", function (evt) {
            return _this.errorLoading(evt);
        }, false);
        */
    },

    willDestroyElement: function(){
        this.$().off('load', 'error');
    },

    imageLoaded: function(event){
        console.log("Image Loaded");
    },

    errorLoading: function(event){
        this.set('src', this.get('defaultImg'));
    }

});

Use:

Controller:

// path to the Expected image
var img = data.imgPath; 

// path to Default / backup image
// change this to match design requirements or default image path.
var defaultImg = 'http://placehold.it/190x200'; 

View:

{{default-img src=img defaultImg=defaultImg}}